Skip to content

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)

  1. Coverage % is NOT the optimization target. Hot-path coverage (production-traffic-weighted) is.
  2. AI agents can write good tests for Raklet — to be validated by the wedge.
  3. Constructor-injection refactor is the right testability pattern — let the AI prove it can do this on the wedge.
  4. PaymentsController is the right wedge — thin controller over OrganisationMembershipService, so the test mocks the service interface, not PayPal/Stripe directly.
  5. ⚠️ 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.yml into a separate coverage-nightly.yml workflow on a cron (e.g. 0 3 * * * UTC), uploading the same Cobertura XML artifact + summary.
  • Add a per-PR coverage-delta.yml workflow 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 on job/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 — legacy Email/Link malformed traffic is guarded with 410 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 through Application/Controllers/HomeController (Tier 1 #4) instead.
  • Raklet.Api/Controllers/App/Membership/SubscriptionController.cs — also not in App Insights top 100; the legacy Application/Controllers/MembershipController carries 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 in Application/, 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 CRM
  • Application/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.

  1. 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 through Application/Controllers/HomeController.Details (the per-org public event-detail page), which sits at 36k hits with p95 = 1.8s. The Raklet.Api EventsController carries the admin/SPA side, which is real traffic on /App GA4 paths but generates fewer direct API hits than the public MVC routes. Implication: start with HomeController-side coverage, not Raklet.Api EventsController.

  2. AccountController dominates 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 in Application/Areas/Login/Controllers/AccountController.cs (3,005 LOC), NOT the 1,251-LOC Raklet.Api/Controllers/AccountController.cs that the previous doc named. Implication: the auth surface is bigger than we thought and must be sub-chunked into 4 separate chunks.

  3. AnnouncementController is on fire in prod. 42.12% failure rate on POST Announcement/Details, 22.46% on GET Announcement/Details, 16.79% on GET 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.

  4. Email/Link failure traffic is mostly legacy/spam 410 noise. Malformed /Email/Link and plain /t tracking requests are rejected before DB/storage work; keep valid legacy-link coverage separate from backlink probe traffic.

  5. Zapier traffic is non-trivial. /app/zapier/payments has 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.

  6. /v2/me and /v2/bootstrap are HUGE blast radius. Bootstrap fires on every SPA session start (19k/month, p99 = 619ms). /v2/me has p95 = 7,024ms — that's a 7-second tail. Both deserve dedicated coverage despite not being in obvious "money" or "auth" buckets.

  7. 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.

  8. POST Apply/Confirm p95 = 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 on RakletDb (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")].

  9. Hot-tables coupling is uneven across Tier 1. ApplyController and the money-bundle (#5, #6) touch the heaviest tables on RakletDb (OrganisationMemberships 6.4 GB + Payments 1.1 GB + Debts 1.1 GB + ApplicationForms 688 MB). AccountController (#2) and the auth surface touch Organisations 3.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

  1. One chunk = one Linear card = one branch = one PR = one AI agent. No mixing.
  2. Chunk 0 lands first. Chunks 2-N are blocked on chunk 0's canonical pattern.
  3. 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.
  4. "What we learned" section is mandatory in every PR description. It feeds chunk-template refinement.
  5. 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.
  6. 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.