---
name: development-testing
description: >
  Generates real test suites per architectural layer with business rule coverage,
  consuming NuGet (xUnit, FluentAssertions, Moq, FluentValidation.TestHelper) and
  npm (Vitest, Testing Library, React Query) packages.
phase: development
cli: cli/
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# Development Testing

Generates and manages test suites for SmartStack client extensions at every
architectural layer: seed, domain, business (with business rule tests),
API integration, and frontend components.

**All generated tests are functional** (no `Assert.True(true, "TODO")` stubs).
They compile, run, and assert real behaviour out of the box. They can fail
the first time if the underlying production code does not respect SmartStack
conventions — that is the intended signal.

## When to Use

- Phase 7 of the development workflow (called once per layer)
- When adding tests to an existing module
- When verifying business rule coverage

## Test Strategy by Layer

| Layer | Category Trait | Type Trait | Frameworks used | What it asserts |
|-------|---------------|------------|-----------------|-----------------|
| Seed | `[Trait("Category","Seed")]` | `[Trait("Type","Unit")]` | xUnit + EF Core InMemory | Idempotence (double-run no duplicates), full hierarchy, permission coverage |
| Domain | `[Trait("Category","Domain")]` | `[Trait("Type","Unit")]` | xUnit + FluentAssertions | Factory `Create()`, Update(), unique ids, field assertions |
| Business (handler) | `[Trait("Category","Business")]` | `[Trait("Type","Unit")]` | xUnit + Moq + FluentAssertions | Handler delegates to `I{E}Service` (Mock + Verify) |
| Business (service) | `[Trait("Category","Business")]` | `[Trait("Type","Unit")]` | xUnit + InMemory `ExtensionsDbContext` + fake tenant + FluentAssertions | Service persists / queries / deletes; named "Tenant" filter exercised + per-entity cross-tenant isolation fact (fails if the TENANT-FILTERS line is unmounted — DEV-API-032) |
| Business (validator) | `[Trait("Category","Business")]` | `[Trait("Type","Unit")]` | xUnit + FluentValidation.TestHelper | `ShouldHaveValidationErrorFor(...)` per required field + one `[Fact]` per rule |
| API | `[Trait("Category","Integration")]` | `[Trait("Type","Integration")]` | xUnit + WebApplicationFactory<Program> + **SQL Server LocalDB** + Respawn | Real routes (PostConfigure JWT): 401 unauthenticated + **denied facts** (`*_WithoutPermission_IsDenied`, exact 403/401 — a token with ZERO permission claims can never get 200: fails on an unguarded endpoint, the runtime twin of DEV-API-033) + the positive fact `GetAll_WithReadPermission_IsNotForbidden` when the entity spec declares its `section` (exact 4-seg permission, never guessed — a 403 with the right claim = broken wiring) + no-5xx probes |
| Frontend | — | — | Vitest + Testing Library + React Query | Render, page loads, QueryClient wiring |
| **UI (Phase 5)** | — | — | dev-browser CLI + Studio runner | Real Chromium navigates each scaffolded page with role-based seeded users, asserts no 4xx/5xx, submits forms, auto-corrects via `claude -p fix-bug` (max 50 iter/test). See `ui-test/SKILL.md`. |

## Test conventions (source of truth)

These generators encode the idioms distilled from the SmartStack.app test corpus.
The deployed client skill **`/test-conventions`** is the per-type reference —
read its `references/<type>.md` before hand-writing or extending any test.
Universal rules applied across every backend layer: `Method_Scenario_Expected`
naming · one test = one behaviour · `#region` per method · factory
`Entity.Create(...)` (never `new`) · `Guid.NewGuid()` (never hardcoded) ·
FluentAssertions 8.x (DateTime → `BeOnOrAfter`/`BeBefore`/`BeCloseTo`, never
`BeGreaterThan`; collections `HaveCount`; exceptions `Throw<T>().WithMessage("*kw*")`)
· xUnit v3 `TestContext.Current.CancellationToken` · `NullLogger<T>.Instance` ·
`Options.Create(...)` · **zero stubs** (`Assert.True(true)` / `expect(true).toBe(true)`).
`audit-dev-tests` flags drift on the last few as DEV-TEST-004 (err — stub
assertions block) and DEV-TEST-005..007 (advisory).

## Key behaviour contracts

### Seed tests
In-memory `CoreDbContext` (`UseInMemoryDatabase(Guid.NewGuid().ToString())`),
`TestContext.Current.CancellationToken`. Call `SeedNavigationAsync` twice to assert
idempotence; check FK alignment (every module has a matching application).

### Domain tests
`${Entity}.Create(...)` with valid args, assert properties + `BeOnOrAfter(before)`
for timestamps; `#region` per method. Call with `null!`/default for required params
to assert `ArgumentException`.

### Business tests — two levels
The generated handler is a thin delegator to `I{E}Service`; the service holds the
logic + EF access. So the tests split:
- **Handler tests** (`{E}HandlersTests`) — bare `Mock<I{E}Service>` + `Verify(...)`
  the delegation. No DbContext.
- **Service tests** (`{E}ServiceTests`) — a REAL in-memory `ExtensionsDbContext`
  built with a `FakeCurrentTenantService` (so the tenant query filter is exercised;
  EF InMemory honours query filters). Asserts Create persists / GetById returns /
  Delete removes. The service injects **`IExtensionsDbContext`** (extension entities
  live in the `extensions` schema; Core lookups go through `ICoreDataService`).
  InMemory does NOT enforce relational constraints, SQL translation, or
  SqlObjects/TVF — the integration layer (below) covers those.
- **Validator tests** — `TestValidate(...).ShouldHaveValidationErrorFor(...)` per
  required field + one `[Fact]` per business rule.

### API / integration tests — REAL SQL Server LocalDB (mandatory)
Integration tests run against a **real SQL Server LocalDB** (never InMemory) via a
`DatabaseFixture` (Respawn reset) + a `{Ext}WebAppFactory : WebApplicationFactory<Program>`:
- Override JWT via `services.PostConfigure<JwtBearerOptions>(...)` — NOT via config
  (read too early by `AddInfrastructure`).
- Mint a token with the shared HMAC key (`JwtTokenHelper`), call REAL routes, assert
  status codes; dispose factory + client (`IAsyncDisposable`).
- Execution pitfalls: a running dev API locks the build output → **MSB3021** (stop
  it, or `dotnet test -p:BuildProjectReferences=false`); use `npm install` (not
  `npm ci`) while a Vite dev server runs (`@tailwindcss/oxide` lock).

The harness templates ship under `templates/project/test-backend/`
(`DatabaseFixture`, `{Ext}WebAppFactory`, `JwtTokenHelper`, `FakeCurrentTenantService`)
and are scaffolded into the client test project.

### Frontend tests
`vi.mock(...)` **HOISTED above the page import**; **mock `react-i18next`**
(`t: (k) => k`); `MemoryRouter` (never `BrowserRouter`); Testing Library queries;
`crypto.randomUUID()`. The shared harness (`test-frontend/test-utils.tsx`) keeps
**MSW** + React Query for integration-style page tests.

## CLI: scaffold-tests

```bash
npx --prefer-offline tsx skills/development/testing/cli/scaffold-tests/index.ts \
  --spec '{"layer":"business","entities":[...],"businessRules":[...],"projectPath":"/path"}'
```

Layer values: `seed | domain | business | api | frontend`.

## CLI: scaffold-tests-from-ac

Generates ONE xUnit `[Fact]` per Acceptance Criterion declared under each Use
Case in `.smartstack/ba/<APP>/<MODULE>/<section>/use-case.md`. The body is a
`// TODO[AC-NN]` skeleton that **Phase 5 of /ba-develop** fills in with the
real assertion. The audit `DEV-TEST-001` (Wave 3) blocks merge if any TODO
remains. AC live in `use-case.md` under a `**Acceptance Criteria**` field —
each bullet is `- [ ] AC-NN — <imperative assertion>`.

```bash
npx --prefer-offline tsx skills/development/testing/cli/scaffold-tests-from-ac/index.ts \
  --spec '{"moduleDir":".smartstack/ba/CRM/PIPELINE","projectPath":"./generated/crm","appCode":"crm","module":"pipeline"}'
```

Spec fields:
- `moduleDir` (required) — absolute or repo-relative path to the BA module folder.
- `projectPath` (required) — root of the .NET solution.
- `appCode` (required, lowercase kebab) — used in the namespace prefix.
- `module` (required, lowercase kebab) — used in the namespace prefix.
- `namespace` (optional) — full namespace override; defaults to `{Pascal(appCode)}.Tests.{Pascal(module)}.Acceptance`.
- `dryRun` (optional) — parse + plan, do not write.

Output: `Tests/{Module}/Acceptance/{Section}AcceptanceTests.cs` (one per section
that has ≥ 1 AC). Every Fact carries `[Trait("AC", "<UC-code>#AC-NN")]` so the
runner can filter and the audit can match BA ACs against generated tests 1:1.

The global AC reference format is `<UC-code>#AC-NN` (e.g.
`UC-CRM-PIPELINE-OPPORTUNITES-001#AC-02`) — both the BA file and the generated
test agree on this id. See `business-analyse/_workflow/doc-templates.md` for the
authoritative AC grammar and `business-analyse/audit-use-cases/SKILL.md`
UC-013..018 for the audit rules.

## CLI: test-report

```bash
npx --prefer-offline tsx skills/development/testing/cli/test-report/index.ts \
  --spec '{"projectPath":"/path","module":"hrm"}'
```

Produces structured JSON report at `.smartstack/reports/TEST-{module}-{timestamp}.json`.

## Test File Naming Convention

```
Tests/Common/FakeCurrentTenantService.cs                ← shared tenant test-double (emitted once)
Tests/{Module}/Seed/{Module}SeedDataProviderTests.cs
Tests/{Module}/Domain/{Entity}Tests.cs
Tests/{Module}/Application/{Entity}HandlersTests.cs     ← handler delegation (Mock<I{E}Service>)
Tests/{Module}/Application/{Entity}ServiceTests.cs      ← service via in-memory ExtensionsDbContext
Tests/{Module}/Application/Create{Entity}CommandValidatorTests.cs
Tests/{Module}/Api/{Plural}ControllerTests.cs           ← integration: LocalDB + Respawn + WebAppFactory
Tests/{Module}/Acceptance/{Section}AcceptanceTests.cs   ← scaffold-tests-from-ac
tests/{module}/{entity}/{Entity}.test.tsx
```

## NuGet / npm packages required in the client project

Backend test project:
- `xunit.v3`, `xunit.runner.visualstudio` (xUnit v3 — `TestContext.Current.CancellationToken`)
- `FluentAssertions` (>= 8.x — see `BeOnOrAfter`, `HaveCount` gotchas)
- `Moq`
- `FluentValidation.TestHelper`
- `Microsoft.EntityFrameworkCore.InMemory` (unit-layer service tests)
- `Microsoft.AspNetCore.Mvc.Testing` (API/integration tests)
- `Respawn` + `Microsoft.Data.SqlClient` (integration tests — real SQL Server LocalDB reset)

Frontend test setup:
- `vitest`, `@testing-library/react`, `@testing-library/user-event`, `@testing-library/jest-dom`
- `@tanstack/react-query`
- `react-router-dom`
- `@atlashub/smartstack` (for `SmartStackProvider` in test providers)
