Skip to content

Test Coverage Chunk — Task Template

This is the canonical task template handed to AI agents (codex, cursor, claude) for each test-coverage chunk. Do not modify the structure without updating RAK-355 first — consistency across chunks is what lets us compare agents and review outputs predictably.

Per chunk, copy this template into the Linear issue description, fill in the <placeholder> values, and assign the issue to one AI agent.


Linear issue title

test(coverage): add test pack for <ControllerName> [<priority-rank>]

Examples: - test(coverage): add test pack for PaymentsController [wedge] - test(coverage): add test pack for SubscriptionController [rank 4]

Linear issue body

## Goal

Land a test pack for `<path/to/Controller.cs>` (<LOC> lines, <N> public methods)
following the pattern established in RAK-355. This is chunk <N> of the
test-coverage initiative.

## Scope — what to write

For every public action method on this controller:

1. **Happy path test** — valid inputs, expected response, expected DB / service side effects asserted via the mocked dependencies.
2. **Auth failure test** — anonymous / unauthorized / missing-role caller; assert 401/403 (or equivalent IHttpActionResult / Result).
3. **Validation failure test** — at minimum one bad-input case per method (null, wrong type, out of range, etc).
4. **Edge case tests** — 2-3 per method, picked by reading the method body for:
   - Async cancellation / timeout paths
   - Empty collections / no results
   - Database concurrency / unique-key violations
   - Currency / unit conversions (for money methods)

If a method has more than 5 branches, write tests for each branch; don't skip them.

## Scope — what to refactor

This controller currently does `new RakletDb()` / `new SomeService()` inline.
For testability, refactor to constructor injection:

- The controller's constructor must accept the service interfaces it uses (e.g. `IPaymentService`, `IOrganisationMembershipService`).
- The legacy parameterless constructor (used by Web API's `IHttpControllerActivator`) can remain as a thin wrapper that news up the concrete services and calls the injectable constructor.
- Do NOT introduce a DI container in this PR. Per-test fixtures + parameterless-default constructor for prod is enough.

## Out of scope — DO NOT touch

- Service-layer code (PaymentService, OrganisationMembershipService internals) — service tests are a separate chunk
- Other controllers (one chunk = one controller)
- Web.config, Global.asax, OWIN startup, routing
- Any file outside `Raklet.Api/Controllers/<area>/`, `Raklet.UnitTests/Controllers/`, and the controller's matching test file
- Test coverage measurement workflow (RAK-340 owns that)

## How to use the scaffolding

This chunk depends on the `Raklet.UnitTests.Infrastructure` namespace landed in
the foundation PR (see the wedge PR for the canonical pattern). Use:

- `FakeDbSet<T>` — in-memory `IDbSet<T>` over `List<T>` for EF6 stub
- `RakletDbFake` — stubbed `RakletDb` with `FakeDbSet<>` properties
- `ControllerTestBase` — base class with `MockHttpContextBuilder`, principal stubs, route data, request URI
- `Moq` for service interface mocks

See `Raklet.UnitTests/Controllers/PaymentsControllerTests.cs` for the canonical reference. Mirror that pattern exactly.

## Definition of done

- [ ] All public methods on `<ControllerName>` have at least happy + auth + 1 edge-case test
- [ ] Controller refactored to accept service interfaces via constructor injection (legacy parameterless ctor wraps it)
- [ ] All tests are tagged `[TestCategory("Unit")]` (NOT Integration — they must run on every PR's Tests workflow)
- [ ] All tests are `async Task` for async methods; sync `void` only for sync methods
- [ ] No test calls `new RakletDb()` directly; all DB access goes through `FakeDbSet`
- [ ] No test hits a real network endpoint; all external API calls are mocked
- [ ] `dotnet test` passes locally before PR opens
- [ ] PR opens against master with a description that links back to RAK-355 and notes which premises the chunk validated/invalidated

## What to report back when done

In the PR description, include a "What we learned" section:

- Did the AI agent need any human-in-the-loop intervention? When?
- Any test patterns that didn't translate cleanly from the wedge?
- Time spent: from "AI starts" to "PR ready for review"
- Anything in the controller code that should be flagged as a refactor candidate beyond what was done in this chunk

This is the data that feeds future chunk sizing and the "AI net-positive" premise check.

## Agent assignment

Agent: <codex | cursor | claude>
Branch: `rakletai/coverage-<controllername-lower>` (matches Linear gitBranchName)
PR target: master
Review gate: CEO approval per memory feedback_master_review_required

Notes for the chunk-author (you, when delegating)

  • One chunk = one agent = one PR. Don't fan out a controller across multiple agents — comparison across agents only works at the controller-grain.
  • Capture which agent did which chunk in the Linear card. After 5-10 chunks land, we'll have real data on codex vs cursor vs claude quality + speed.
  • Don't skip the "what we learned" section of the PR. That section is the actual product of this whole initiative — without it, we can't refine the chunking template, can't size the rest of the roadmap, and can't tell whether the AI-shipping-confidence bet is paying off.
  • Pause new chunks after the wedge until we have the wedge PR landed and reviewed. The whole point of the wedge is to find what's wrong with this template before we replicate it 50 times.