Test Coverage — Delegatable Chunks¶
Operational backlog of test-coverage chunks for AI-agent delegation, derived
from the /office-hours session on 2026-05-22 against
test-coverage-plan.md.
Read order: 1. test-coverage-plan.md — the strategy, the math, the premises 2. test-coverage-task-template.md — what each chunk looks like as a Linear issue 3. This file — the actual ordered backlog
Source of priority ranking: App Insights top-routes (30d) + GA4 top-pages (30d) + the hot-tables registry (prod table sizes, 2026-05-14). All three signals landed by 2026-05-22. The hot-tables doc replaces what we'd thought we needed sys.dm_db_index_usage_stats for — it tells us which tables EF queries can mistranslate at prod scale, which is what matters for choosing between FakeDbSet unit tests vs. real-SQL integration tests. See What surprised us about the traffic for the data-driven re-rank.
Premises this list rests on (from office-hours)¶
- ❌ Coverage % is NOT the optimization target. Hot-path coverage (production-traffic-weighted) is.
- ✅ AI agents can write good tests for Raklet — to be validated by the wedge.
- ✅ Constructor-injection refactor is the right testability pattern — let the AI prove it can do this on the wedge.
- ✅ PaymentsController is the right wedge — thin controller over
OrganisationMembershipService, so the test mocks the service interface, not PayPal/Stripe directly. - ⚠️ CI runner saturation is a real risk — move coverage step to a nightly cron with per-PR delta check (Phase 1).
Chunk index¶
| # | Chunk | Status | Agent | LOC | Linear | Notes |
|---|---|---|---|---|---|---|
| 0 | Foundation + PaymentsController wedge | 🟢 ready to ship | TBD (codex recommended) | 83 + ~400 scaffold | RAK-355 sub-issue | One PR. Includes Moq, FakeDbSet, ControllerTestBase, MockHttpContextBuilder, async test pattern, AND the PaymentsController test pack as the reference implementation. Ship this first. Block all other chunks until this lands. |
| 1 | Move coverage to nightly + per-PR delta check | 🟢 ready to ship | claude or human (small) | n/a | RAK-355 sub-issue | Premise 5 fix. ~half day. Independent of all other chunks — can ship in parallel with chunk 0. |
| 2-N | Controller chunks ranked by hot-path data | 🟢 ranked (DB-usage axis pending) | TBD | varies | TBD (after chunk 0 lands) | Data-driven ranking below |
Chunk 0 — Foundation + PaymentsController wedge¶
This is the unblock-everything chunk. Until this lands, no other chunk can start.
- What: Add Moq, build
Raklet.UnitTests.Infrastructure/{FakeDbSet, RakletDbFake, ControllerTestBase, MockHttpContextBuilder}.cs, document the async test pattern, then write the canonical PaymentsController test pack using all of it. - Why first: It's the reference implementation. Every subsequent chunk imitates it.
- Wedge purpose: Validates that AI agents can write good tests for this codebase. If they can't, the whole 80% plan needs rethinking. The "what we learned" section of this PR is THE most important artifact in the whole initiative.
- Size: 3-7 days (sized generously because it's the learning experiment per Q5 of office-hours).
- Reviewer: CEO must approve per memory
feedback_master_review_required. - Branch name:
rakletai/coverage-foundation-payments
Chunk 1 — Move coverage to nightly + per-PR delta (Premise 5 fix)¶
- What: Two CI changes:
- Extract the coverage-running step from
tests.ymlinto a separatecoverage-nightly.ymlworkflow on a cron (e.g.0 3 * * *UTC), uploading the same Cobertura XML artifact + summary. - Add a per-PR
coverage-delta.ymlworkflow that pulls the latest nightly artifact, runs the unit-test suite WITH coverage in <2 minutes, and compares against the nightly baseline. Fails PR only if delta is more negative than -0.5%. - Why: Without this, every PR pays the 3-5 min coverage overhead on ci-vm-1 and the queue gets worse as test count grows.
- Size: ~half day for one engineer. Independent of chunk 0; can ship in parallel.
- Branch name:
rakletai/coverage-nightly-and-delta
Chunks 2-N — Controller test packs (data-driven ranking, 2026-05-22)¶
Ranking method: score = (30-day request count) × (severity multiplier), aggregated by controller from the App Insights requests table (top 100 routes by hits) plus GA4 top-100 pagePath cross-check. Severity multipliers: money=5, auth=4, pii_write=3, pii_read=2, public_read=1.5, tracking=1, internal=0. Raw exports + classifier script live in .data/ (gitignored — see reference_appinsights_ga4 memory).
Tier 1 — data-validated highest priority (start here after chunk 0)¶
The Hot tables column lists prod hot tables (per hot-tables.md) the controller's services touch. If a controller hits any hot table, its service-layer chunk MUST include real-SQL [TestCategory("Integration")] tests per hot-tables.md rule 1 — FakeDbSet unit tests at the controller boundary aren't sufficient on their own.
| Rank | Controller (primary file) | 30d hits | Score | Severity | Hot tables touched | Why this score |
|---|---|---|---|---|---|---|
| 2 | Application/Areas/Login/Controllers/AccountController.cs (3,005 LOC) + Application/Controllers/AccountController.cs (141) — the legacy MVC auth stack that serves Account/Login, Account/LoginWithEmail, Account/Logout, Account/Welcome*, Account/ConfirmationEmailSent, Account/user, /account/navigation, quickregister/Index, etc. |
270,833 across 11 routes | 1,083,332 | auth (×4) | Organisations (34k / 3.9 GB), OrganisationMemberships (910k / 6.4 GB) |
Dominates by 3× over #3. Every signed-in pageload hits /account/navigation. Must sub-chunk — split into (a) Login + LoginWithEmail + OAuth, (b) Logout + session, (c) Welcome/Confirmation/PasswordReset flows, (d) /account/navigation + /Account/user JSON endpoints. Tenant-resolution touches the two biggest RakletDb tables — service-layer chunk needs integration tests. |
| 3 | Application/Controllers/AnnouncementController.cs (2,231 LOC) |
240,116 | 360,174 | public_read (×1.5) | OrganisationMemberships (membership-gating), Organisations |
Active bug surface — 42.12% failure rate on POST Announcement/Details, 22.46% on GET Announcement/Details, 16.79% on GET Announcement/Index. Tests here will catch regressions on a route that's already misbehaving. Worth filing a separate prod-bug investigation in parallel. The high failure rate may itself be a prod-scale EF translation issue — hot-tables rule applies. |
| 4 | Application/Controllers/HomeController.cs (40 LOC public shim) and the actual per-org event-detail handlers it routes to |
118,303 across 9 routes | 188,538 | public_read (×1.5) | Organisations, OrganisationMemberships (for member-only events) |
This is the actual "Events" surface end-users hit. The Raklet.Api EventsController (admin-side flagship) only appears in GA4 (/App SPA), not in App Insights top 100. Includes Home/Details (event landing, p95 ≈ 1.8s — perf concern, classic hot-table-fan-out symptom), Home/AjaxFutureEventsByOrganisation, Home/AjaxPastEventsByOrganisation, Home/GetEventsByDate, Home/Register. Confirms the biggest finding — see surprises section. |
| 5 | Application/Controllers/ApplyController.cs (1,996 LOC) |
34,380 across 5 routes | 171,900 | money (×5) | ApplicationForms (144k / 688 MB), Payments (474k / 1.1 GB), OrganisationMemberships (910k / 6.4 GB), Debts (1.5M / 1.1 GB) |
The actual flagship money flow. Apply/Index 10.59% failure rate, Apply/CreditCardForm + Apply/Confirm + Apply/CheckEmailExistInOrganisation. p95 on Apply/Confirm is 6.9s (!) — the 7-second tail is almost certainly hot-table fan-out at apply-confirm time. Highest hot-table exposure of any controller — service chunk needs aggressive integration coverage. Coverage here protects revenue. |
| 6 | Application/Controllers/MembershipController.cs (1,697 LOC) + Application/Controllers/SignupController.cs (1,700 LOC) + Application/Controllers/PaymentController.cs (2,858 LOC) |
19,616 combined | ~98k combined | money (×5) | Payments (474k / 1.1 GB), OrganisationMemberships (910k / 6.4 GB), Debts (1.5M / 1.1 GB) |
Suggest bundle into one money-flow chunk because they share the same OrganisationMembershipService + Stripe/PayPal mocks built in chunk 0. Three controllers, similar pattern. Same hot tables as ApplyController — integration tests required at the service layer. |
Tier 2 — meaningful traffic, lower urgency¶
| Rank | Controller | 30d hits | Score | Severity | Notes |
|---|---|---|---|---|---|
| 7 | Application/Controllers/TrackController.cs (323 LOC) |
240,100 | 240,100 | tracking (×1) | Highest raw hit count but read-only and low-severity. Thin tests on the URL-redirect logic + click attribution. |
| 8 | Raklet.Api /v2/bootstrap + /v2/me handlers |
25,893 | 89,830 | auth/pii_read | Session bootstrap fires on every SPA load; correctness here is critical. |
| 9 | Raklet.Api/Controllers/App/Zapier/ZapierPaymentsController.cs |
14,915 | 74,575 | money (×5) | High Zapier integration traffic; surprising — confirms Zapier is a serious distribution channel. |
| 10 | Application/Areas/Login/Controllers/SwitchOrganisationController.cs (292 LOC) + SelectOrganisationController.cs (199 LOC) |
14,489 | 57,956 | auth (×4) | Org-switching = auth-tenant boundary; logic bugs here = data leak risk. |
| 11 | Application/Controllers/AnnouncementController.cs POST handlers |
sub-set of #3 | — | pii_write (×3) | Already covered in #3 sub-chunking. |
| 12 | Application/Controllers/MessageCountController.cs + NotificationCountController.cs |
96,182 combined | 96,182 | tracking (×1) | Together they fire 96k/month from every signed-in user. Worth a small shared chunk. |
| 13 | Raklet.Api/Controllers/App/Connections/TagsController.cs |
11,822 | 23,644 | pii_read | The most-hit /v2/... resource by far. |
| 14 | Raklet.Api/Controllers/App/Connections/MembersController.cs (NOT to be confused with Application/Controllers/Members) |
8,931 | 17,862 | pii_read | 12.59% failure rate on Members/Index. |
| 15 | Raklet.Api/Controllers/App/Profile/* (~10 sibling controllers, e.g. ProfileSettings, ProfileAddresses, ProfileEmails) |
21,685 GA4 events on /App/Profile |
— | pii_read/write | GA4 says users live in /App/Profile heavily; App Insights doesn't break out by sub-controller. Bundle into one Profile chunk — they share the same pattern. |
Tier 3 — long tail (defer until tier 1+2 done)¶
Application/Controllers/SearchController.cs(9,861 hits, score 14,791)Application/Controllers/PagesController.cs(9,775 hits, score 14,662)Application/Controllers/JobController.cs(13,725 hits, score 20,587, 8.4% fail rate onjob/Index)Application/Controllers/NewslettersController.cs(5,583 hits, 1 PII write route)Application/Controllers/ProfileController.cs(Raklet.Crm side, 6,755 hits)Raklet.Backend/Controllers/EmailController.cs— legacyEmail/Linkmalformed traffic is guarded with410 Gone; cover valid legacy links plus the malformed-link guard, not spam/backlink probe traffic.Raklet.Api/Controllers/App/Events/EventsController.cs(800 LOC) — demoted from previous Tier 1. Does NOT appear in App Insights top 100. The/Events/*GA4 traffic routes throughApplication/Controllers/HomeController(Tier 1 #4) instead.Raklet.Api/Controllers/App/Membership/SubscriptionController.cs— also not in App Insights top 100; the legacyApplication/Controllers/MembershipControllercarries the load.Raklet.Api/Controllers/App/Contact/ContactController.cs— appears at 3,928 hits (/organisations/{guid}/contacts), score 7,856. Still useful but not Tier 1.Raklet.Api/Controllers/AccountController.cs(1,251 LOC) — the API-side one. The high traffic is on the MVC AccountControllers inApplication/, not this. Confirmed via routing inspection.
Tier 4 — admin / internal (lowest user-facing priority)¶
Application/Areas/Manager/Controllers/**— admin-facing, lower per-controller traffic but higher blast-radius per action (mass-update operations)Raklet.Crm/**— internal CRMApplication/Areas/App2/Controllers/**— older end-user pages now served by Raklet.Api equivalents
Backlog (defer until tier 1+2 done)¶
- Service-layer tests (
Services/— ~149 files at 2.6% — needs its own chunking pass after we know how controller chunks went) Raklet.Backend/TypeScript Jest tests (separate stack, parallel stream)- Long-tail utility libs (
Raklet.Money,.Sms,.Email, etc.)
What surprised us about the traffic¶
Findings from the 2026-05-22 30-day data pull. These shifted the ranking materially from the office-hours-session vibe.
-
EventsController(the admin-side Raklet.Api flagship) is NOT in the top 100 routes. The previous chunks doc had it at rank 2 because "Events is a Raklet flagship feature." The data says end-user event traffic flows throughApplication/Controllers/HomeController.Details(the per-org public event-detail page), which sits at 36k hits with p95 = 1.8s. The Raklet.ApiEventsControllercarries the admin/SPA side, which is real traffic on/AppGA4 paths but generates fewer direct API hits than the public MVC routes. Implication: start with HomeController-side coverage, not Raklet.Api EventsController. -
AccountControllerdominates by 3×. Aggregated across 11 distinct auth routes (Login, LoginWithEmail, Logout, Welcome, ConfirmationEmailSent, password reset, OAuth,/account/navigation,/Account/user, quickregister) it scores 1.08M — 3× the next-biggest. And the real LOC sits inApplication/Areas/Login/Controllers/AccountController.cs(3,005 LOC), NOT the 1,251-LOCRaklet.Api/Controllers/AccountController.csthat the previous doc named. Implication: the auth surface is bigger than we thought and must be sub-chunked into 4 separate chunks. -
AnnouncementControlleris on fire in prod. 42.12% failure rate onPOST Announcement/Details, 22.46% onGET Announcement/Details, 16.79% onGET Announcement/Index. These aren't 4xx-from-spam-bots — at this scale (240k hits/month) it's a real bug. Action: file a prod-bug investigation card in parallel; don't just add tests on top of a broken controller. -
Email/Linkfailure traffic is mostly legacy/spam410noise. Malformed/Email/Linkand plain/ttracking requests are rejected before DB/storage work; keep valid legacy-link coverage separate from backlink probe traffic. -
Zapier traffic is non-trivial.
/app/zapier/paymentshas 14,915 hits/month with 0 failures — Zapier is a serious distribution channel and ZapierPaymentsController is a quiet hot path that nobody mentioned in office-hours. -
/v2/meand/v2/bootstrapare HUGE blast radius. Bootstrap fires on every SPA session start (19k/month, p99 = 619ms)./v2/mehas p95 = 7,024ms — that's a 7-second tail. Both deserve dedicated coverage despite not being in obvious "money" or "auth" buckets. -
The previous Tier 1 (Events / Subscription / Contact / API AccountController) was 60% wrong. Three of four didn't survive the data check. The wedge premise (PaymentsController as chunk 0) is still right — it's a clean thin controller for the learning experiment — but chunks 2-N look very different from the office-hours guess.
-
POST Apply/Confirmp95 = 6,947ms. Money-flow confirmation taking ~7 seconds at the tail. Tests should assert a perf budget on the unit-test path even if real Stripe/PayPal calls dominate the prod latency. ApplyController touches the four most-fanned-out hot tables onRakletDb(see Tier 1 #5) — this latency is almost certainly EF fan-out, not Stripe/PayPal, and the service-layer chunk should reproduce it under[TestCategory("Integration")]. -
Hot-tables coupling is uneven across Tier 1. ApplyController and the money-bundle (#5, #6) touch the heaviest tables on
RakletDb(OrganisationMemberships6.4 GB +Payments1.1 GB +Debts1.1 GB +ApplicationForms688 MB). AccountController (#2) and the auth surface touchOrganisations3.9 GB on every signed-in pageload — so/account/navigation(152k hits/month) is paying that lookup cost 5k times a day. Implication: the service-layer chunks behind Tier 1 controllers are NOT all equal — money + auth must lead with integration tests against real SQL; tracking and read-only chunks can stay FakeDbSet-only.
What's still pending¶
Nothing on the data side. Earlier drafts of this doc listed sys.dm_db_index_usage_stats as missing — hot-tables.md supersedes that need by answering the practical question (which tables are big enough to break naive EF queries at prod scale). Background-job coverage (anything that doesn't generate App Insights requests) will need its own chunking pass later, but that's a backlog issue, not blocking Tier 1.
Operational rules¶
- One chunk = one Linear card = one branch = one PR = one AI agent. No mixing.
- Chunk 0 lands first. Chunks 2-N are blocked on chunk 0's canonical pattern.
- Capture agent attribution in every chunk's Linear card. After 5-10 chunks land we'll have real data on which agent works best for our codebase.
- "What we learned" section is mandatory in every PR description. It feeds chunk-template refinement.
- Stop after the first 5 chunks and re-assess. If AI tests are net-negative on engineer time, pause and revisit the whole initiative. The wedge is the experiment, but the next 4 chunks confirm or deny the pattern.
- Don't pre-create Linear cards for chunks 2-N. Per memory
feedback_match_capture_to_commitment, those are parked analysis until the data lands and we commit to specific controllers.
What needs to happen before chunks 2-N can start¶
- [ ] CEO grants Application Insights access (per office-hours D3)
- [ ] CEO grants Google Analytics access (per office-hours D3)
- [ ] CEO grants DB query-pattern access (per office-hours D3)
- [ ] Chunk 0 (Foundation + PaymentsController wedge) lands and is reviewed
- [ ] The "What we learned" section of chunk 0 informs:
- [ ] Whether chunking pattern is right
- [ ] Whether constructor-injection refactor pattern is right
- [ ] Whether AI-write + human-review is net-positive
After all the above, re-rank chunks 2-N and create their Linear cards (one card per chunk), then dispatch in parallel across codex + cursor + claude.