Skip to content

Commit c7e36be

Browse files
MrHinshclaude
andauthored
migrate: Reqnroll → internal DSL + canonical TestCategory tags across all test files (#126)
## Summary - **Migrate Reqnroll `.feature` families to code-first MSTest internal DSL** — 320 commits covering assessment, DSL design, extraction, conversion, refactor, and verification for each family - **Apply canonical `TestCategory` tags** to every test file in the repository (`CodeTest`+`UnitTests`, `CodeTest`+`DomainTests`, `CodeTest`+`IntegrationTests`, `SystemTest`+`SystemTest_Simulated`, `SystemTest`+`SystemTest_Live`) - **Remove all `[Ignore]` attributes** — 6 previously-ignored tests now pass by implementing missing production seams and DSL abstractions - **Guardrail improvements** — `SystemTest_Smoke` locked to operator-only assignment; `[Ignore]` and `Assert.Inconclusive` bans enforced in skill SKILL.md files; `SkipIfNotConfigured` → `FailIfNotConfigured` / `SkipIfInvalidToken` → `FailIfInvalidToken` in `SystemTestEnvironment` ## Fully migrated feature families `tui-job-detail`, `tui-job-direct-jump`, `tui-job-list`, `tui-diagnostics-panel`, `tui-job-submission-output`, `dependency-command-wiring`, `workitem-inventory`, `export-follow-and-level`, `tfs-export`, `import-default-team-detection`, `import-team-area-paths`, `import-team-definitions`, `tfs-field-projection`, `filter-scope-inventory`, `dependency-pre-filter`, `inventory-modules` (ado/simulated/tfs), `commands-execute-successfully` ## Partially migrated - `system-test-ci-execution` — 4/5 scenarios retired; scenario 1 requires live ADO org (`SystemTest_Live`) - `system-test-local-execution` — 4/5 scenarios retired; scenario 1 requires live ADO org (`SystemTest_Live`) ## Remaining feature files (8) `system-test-ci-execution.feature`, `system-test-local-execution.feature`, `inventory-multi-org` (ado/simulated/tfs), `US2-pure-capture-handlers.feature`, `inventory-field-projection.feature`, `discover-work-items.feature` — pending a follow-up workflow run. ## Known outstanding issue `TuiJobDetail_WhenSseConnectionDrops_LogViewReconnectsWithExponentialBackOff` — test documents intended reconnect/backoff behaviour not yet implemented in `TuiLogView`. Tracked as a separate task. ## Test plan - [ ] All `CodeTest` tests pass (`dotnet test --filter "TestCategory=CodeTest"`) — **148 passing, 0 failing** verified locally - [ ] No `[Ignore]` attributes remain in committed test code - [ ] No `Assert.Inconclusive` outside operator-approved `SystemTest_Live` paths - [ ] All touched test files carry both parent and specific `TestCategory` tags 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 56c3d21 commit c7e36be

423 files changed

Lines changed: 13173 additions & 1623 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/20-guardrails/workflow/testing-rules.md

Lines changed: 60 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,53 @@ MSTest conventions, test naming, and organisation. See also: [coding-standards.m
66

77
## Test Priority Hierarchy
88

9-
| Priority | Category | Marker | Speed | Use |
10-
| --- | --- | --- | --- | --- |
11-
| 1 (highest) | Unit Tests | `[TestCategory("UnitTests")]` | < 50 ms | All logic, branching, transforms. No I/O, no DI. Single class/method in isolation. |
12-
| 2 | Domain Tests (Internal DSL + MSTest) | `[TestCategory("DomainTests")]` | < 500 ms | Business behaviour across collaborating domain objects via the internal DSL. |
13-
| 3 | Simulated System Tests | `[TestCategory("SystemTest_Simulated")]` | < 10 s | End-to-end with `Simulated` connector. No network. |
14-
| 3a | Smoke System Tests | `[TestCategory("SystemTest_Smoke")]` | < 30 s | Critical-path subset of system tests run on every PR. |
15-
| 4 (lowest) | Live System Tests | `[TestCategory("SystemTest")]`/`[TestCategory("SystemTest_Live")]` | < 60 s | Requires live ADO/TFS. Environment-gated. |
16-
17-
### Distinguishing UnitTests from DomainTests
9+
> **Speed Budget is not a classifier.** A test's category is determined solely by its intent and makeup — what it exercises and what dependencies it uses. A test that exceeds its category's speed budget is non-compliant and must be fixed (e.g. inject a time abstraction, replace real delays with fakes) — not moved to a slower category to make the number fit.
1810
19-
| Criterion | UnitTests | DomainTests |
20-
| --- | --- | --- |
21-
| Scope | Single class/method in isolation | Business behaviour across collaborating domain objects |
22-
| Dependencies | All dependencies mocked/stubbed | Uses real domain objects, DSL builders/runners/assertions |
23-
| DSL usage | No `DevOpsMigrationPlatform.Testing` usage | Uses the internal DSL library (builders, runners, assertions) |
24-
| Arrange style | Direct `new Foo()` + mock setup | Builder pattern (`A.WorkItem().WithField(...)`) |
25-
| Assert style | Assert on return value / state of one object | Assert on observable business outcome |
26-
| I/O | None | None (still in-process, no connectors) |
11+
Tests are grouped into two parent families that reflect the runtime requirement:
2712

28-
**Rule:** If the test references `DevOpsMigrationPlatform.Testing` DSL infrastructure (builders, runners, or domain assertions), it is a `DomainTests` test. Otherwise it is a `UnitTests` test.
13+
- **`[TestCategory("CodeTest")]`** — runs entirely in-process. No system must be active. Covers priorities 1–3.
14+
- **`[TestCategory("SystemTest")]`** — requires the full system to be active. Covers priorities 4–6.
2915

30-
**Principles:** Fast validation is the goal. Push tests downward (can it be a unit test?). Live tests are a last resort. Simulated replaces live where possible. CI gates run UnitTests + DomainTests by default.
16+
Every test carries **both** the specific category tag and its parent family tag.
3117

32-
**Anti-patterns (instant reject):** Simulated/Live test for logic with no external dependency. Feature test with real I/O when mocks suffice. New Live test without proving lower level can't cover it. Feature/Simulated/Live outnumbering Unit + Domain tests.
18+
| Priority | Parent | Specific Marker | Intent & Makeup | Speed Budget |
19+
| --- | --- | --- | --- | --- |
20+
| 1 | `CodeTest` | `[TestCategory("UnitTests")]` | Single class in isolation. All deps mocked. No I/O, no real infrastructure. | < 50 ms |
21+
| 2 | `CodeTest` | `[TestCategory("DomainTests")]` | Business behaviour via the internal DSL. Real domain objects, no connectors or infrastructure. | < 500 ms |
22+
| 3 | `CodeTest` | `[TestCategory("IntegrationTests")]` | Real infrastructure components (e.g. retry policies, HTTP clients, serialisers) wired together in-process. No external network or connector. | < 30 s |
23+
| 4 | `SystemTest` | `[TestCategory("SystemTest_Smoke")]` | Critical-path subset of system tests run on every PR. | < 120 s |
24+
| 5 | `SystemTest` | `[TestCategory("SystemTest_Simulated")]` | End-to-end with the `Simulated` connector. No network. | < 60 s |
25+
| 6 | `SystemTest` | `[TestCategory("SystemTest_Live")]` | Requires live ADO/TFS. Environment-gated. | < 300 s |
26+
27+
### Distinguishing categories
28+
29+
| Criterion | UnitTests | DomainTests | IntegrationTests |
30+
| --- | --- | --- | --- |
31+
| Scope | Single class/method in isolation | Business behaviour across collaborating domain objects | Real infrastructure components wired together in-process |
32+
| Dependencies | All mocked/stubbed | Real domain objects via DSL builders/runners/assertions | Real library/framework components (e.g. Polly, HttpClient) — no external network |
33+
| DSL usage | No `DevOpsMigrationPlatform.Testing` usage | Uses the internal DSL library (builders, runners, assertions) | No DSL |
34+
| Arrange style | Direct `new Foo()` + mock setup | Builder pattern (`A.WorkItem().WithField(...)`) | Direct construction of real infrastructure components |
35+
| Assert style | Assert on return value / state of one object | Assert on observable business outcome | Assert on real component behaviour (retry count, response, serialised output) |
36+
| I/O | None | None | None (in-process only; no filesystem, no network) |
37+
| External connectivity | None | None | None |
38+
39+
**Classification rules:**
40+
- If the test references `DevOpsMigrationPlatform.Testing` DSL infrastructure → `[TestCategory("CodeTest")]` + `[TestCategory("DomainTests")]`
41+
- If the test uses real library/framework components in-process with no external connectivity → `[TestCategory("CodeTest")]` + `[TestCategory("IntegrationTests")]`
42+
- If the test is a single isolated class with all deps mocked → `[TestCategory("CodeTest")]` + `[TestCategory("UnitTests")]`
43+
- If the test exercises a full end-to-end flow with the Simulated connector → `[TestCategory("SystemTest")]` + `[TestCategory("SystemTest_Simulated")]`
44+
- If the test requires live ADO/TFS → `[TestCategory("SystemTest")]` + `[TestCategory("SystemTest_Live")]`
45+
46+
> **`SystemTest_Smoke` — OPERATOR-DESIGNATED ONLY.**
47+
> An agent MUST NOT add `[TestCategory("SystemTest_Smoke")]` to any test under any circumstances.
48+
> Smoke tests are a specific designated subset curated by a human operator. Only a human operator
49+
> can assign this category, in writing (e.g. PR comment or decision record). If you believe a test
50+
> belongs in the smoke suite, note it in your output summary for the operator to decide — do not
51+
> apply the tag yourself.
52+
53+
**Principles:** Push tests downward — can it be a unit test? Live tests are a last resort. Simulated replaces live where possible. CI gates run `CodeTest` by default; `SystemTest` requires the full system active.
54+
55+
**Anti-patterns (instant reject):** Integration/Simulated/Live test for logic that could be mocked. New Live test without proving lower level can't cover it. Integration/Simulated/Live outnumbering Unit + Domain tests. Agent-assigned `SystemTest_Smoke`.
3356

3457
---
3558

@@ -40,23 +63,27 @@ MSTest conventions, test naming, and organisation. See also: [coding-standards.m
4063
4164
Every time a test file is **created, edited, moved, or touched in any way**, every `[TestMethod]` and `[TestClass]` in that file MUST carry the correct `[TestCategory]` before the change is committed or reported as complete.
4265

43-
| Condition | Required attribute |
66+
| Condition | Required attributes (both must be present) |
4467
| --- | --- |
45-
| Test uses `DevOpsMigrationPlatform.Testing` DSL | `[TestCategory("DomainTests")]` |
46-
| Test is isolated unit test (no DSL, no I/O) | `[TestCategory("UnitTests")]` |
47-
| Test uses `Simulated` connector end-to-end | `[TestCategory("SystemTest_Simulated")]` |
48-
| Test is a critical-path smoke subset | `[TestCategory("SystemTest_Smoke")]` |
49-
| Test targets live ADO/TFS | `[TestCategory("SystemTest")]` or `[TestCategory("SystemTest_Live")]` |
68+
| Test is isolated unit test (no DSL, no I/O, all deps mocked) | `[TestCategory("CodeTest")]` + `[TestCategory("UnitTests")]` |
69+
| Test uses `DevOpsMigrationPlatform.Testing` DSL | `[TestCategory("CodeTest")]` + `[TestCategory("DomainTests")]` |
70+
| Test uses real infrastructure components in-process, no external connectivity | `[TestCategory("CodeTest")]` + `[TestCategory("IntegrationTests")]` |
71+
| Test uses `Simulated` connector end-to-end | `[TestCategory("SystemTest")]` + `[TestCategory("SystemTest_Simulated")]` |
72+
| Test targets live ADO/TFS | `[TestCategory("SystemTest")]` + `[TestCategory("SystemTest_Live")]` |
73+
| ⛔ Smoke test (operator-designated only) | DO NOT assign — operator only |
74+
| Test uses `Simulated` connector end-to-end | `[TestCategory("SystemTest")]` + `[TestCategory("SystemTest_Simulated")]` |
75+
| Test targets live ADO/TFS | `[TestCategory("SystemTest")]` + `[TestCategory("SystemTest_Live")]` |
5076

5177
**Enforcement rules — all are blocking, none are optional:**
5278

53-
1. **Missing tag on touch:** If any `[TestMethod]` or `[TestClass]` in a touched file lacks `[TestCategory]`, add the correct tag in the same edit. This applies to every method in the file, not just the method being modified.
54-
2. **Wrong tag on touch:** If a tag is incorrect (wrong category, old name), correct it in the same edit.
55-
3. **Delegation does not exempt:** If a sub-agent or delegated run added or modified a test, the calling agent is responsible for verifying tags before closing the task. "The delegated run didn't add it" is not a valid completion state.
56-
4. **No partial compliance:** Applying the tag to the new method while leaving existing uncategorised methods in the same file is non-compliant. Fix the whole file.
57-
5. **Category names are canonical:** Only the exact strings `UnitTests`, `DomainTests`, `SystemTest_Simulated`, `SystemTest_Smoke`, `SystemTest`, `SystemTest_Live` are valid. Any other value is non-compliant and must be corrected on contact.
58-
6. The `nkda-testdsl-*` skills must apply `[TestCategory("DomainTests")]` to all converted tests.
59-
7. The `nkda-testdsl-refactor` skill must verify and correct all category tags in any file it touches.
79+
1. **Missing tag on touch:** If any `[TestMethod]` or `[TestClass]` in a touched file lacks `[TestCategory]`, add the correct tags in the same edit. This applies to every method in the file, not just the method being modified.
80+
2. **Both tags required:** Every test must carry its parent family tag (`CodeTest` or `SystemTest`) AND its specific category tag. A test with only one of the two is non-compliant.
81+
3. **Wrong tag on touch:** If a tag is incorrect (wrong category, old name), correct it in the same edit.
82+
4. **Delegation does not exempt:** If a sub-agent or delegated run added or modified a test, the calling agent is responsible for verifying tags before closing the task. "The delegated run didn't add it" is not a valid completion state.
83+
5. **No partial compliance:** Applying the tag to the new method while leaving existing uncategorised methods in the same file is non-compliant. Fix the whole file.
84+
6. **Category names are canonical:** Only the exact strings `CodeTest`, `UnitTests`, `DomainTests`, `IntegrationTests`, `SystemTest`, `SystemTest_Smoke`, `SystemTest_Simulated`, `SystemTest_Live` are valid. Any other value is non-compliant and must be corrected on contact.
85+
7. The `nkda-testdsl-*` skills must apply `[TestCategory("CodeTest")]` + `[TestCategory("DomainTests")]` to all converted tests.
86+
8. The `nkda-testdsl-refactor` skill must verify and correct all category tags in any file it touches.
6087

6188
**Checklist before marking any test-touching task complete:**
6289

.agents/skills/nkda-testdsl-feature-conversion/SKILL.md

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,46 +45,30 @@ There is no executing baseline, so behaviour parity against prior tests is not a
4545

4646
## Scenario Retirement Gate
4747

48-
- Scenario-level retirement is allowed during conversion, but only for scenarios whose mapped code-first tests are already passing.
49-
- If a scenario's mapped test is failing or unresolved, keep that scenario in the `.feature` file.
50-
- Record, per scenario row in `00-scenario-test-inventory.md`, whether it is retained or retired, with test evidence.
51-
- If all scenarios in a family have been retired, mark the `.feature` file as eligible for deletion; actual file deletion occurs only in verification after overall `PASS`.
48+
- A scenario is retired from the `.feature` file when its mapped DSL test is **passing**.
49+
- A scenario whose test is failing must have its production code fixed until the test passes — only then is it retired.
50+
- Record, per scenario row in `00-scenario-test-inventory.md`, whether it is retained or retired, with test evidence (`path:line`).
51+
- All scenarios must be retired by end of conversion. If all scenarios are retired, mark the `.feature` file as eligible for deletion; actual file deletion occurs only in verification after overall `PASS`.
5252

53-
## Gap Logging
53+
## Completion Standard
5454

55-
Any scenario that cannot be converted MUST be logged in `analysis/dsl-gaps-detected.md` before `04-conversion-summary.md` is finalised. Do not leave a retained scenario undocumented.
55+
Every scenario in the `.feature` file MUST result in a passing DSL test. There is no other acceptable outcome.
5656

57-
For each retained scenario, append an entry using this format:
57+
Per the project guardrails (`definition-of-done.md`, `testing-rules.md`):
58+
- `[Ignore]` is banned. Never add it.
59+
- `Assert.Inconclusive` is banned. Never add it.
60+
- A failing test is not acceptable output — fix the production code until the test passes.
61+
- Retaining a scenario in the `.feature` file because the test is hard to write is not acceptable.
5862

59-
```markdown
60-
## GAP-NNN: <family> — <scenario title>
61-
62-
**File:** `<feature file path>`
63-
**Scenario:** `<scenario title>`
64-
**Family:** `<family>`
65-
**Wiring:** `<wired|miswired|unwired>`
66-
**Gap type:** `<gap-type from reference table>`
67-
**Detected:** `<ISO date>`
68-
**Status:** OPEN
69-
70-
### Engineering detail
71-
72-
<Specific, actionable description of why this scenario cannot be converted.
73-
Reference exact file paths and line numbers where relevant. State what
74-
would need to change to unblock conversion.>
75-
```
76-
77-
Gap number (`NNN`) must be unique — scan `analysis/dsl-gaps-detected.md` for the highest existing number and increment.
63+
**If production code is missing: implement it** — following all guardrails (coding-standards.md, architecture-boundaries.md, DI wiring rules).
64+
**If a seam or interface is missing: create it** — following the canonical seam patterns in the guardrails.
65+
**If a DI binding is missing: add it** — register in the correct host startup extension.
66+
**If production behaviour conflicts with the scenario intent: fix the production behaviour** — the scenario describes the correct behaviour.
67+
**If a subprocess cannot be intercepted: introduce the abstraction** that makes it interceptable, then wire it through DI.
7868

7969
## Stop Conditions
8070

81-
Stop and report if:
71+
Stop and report ONLY if:
8272

83-
- behaviour parity cannot be shown for a `wired` family
84-
- for a `miswired`/`unwired` family, an assertion cannot be confirmed against observed production behaviour, or feature intent conflicts with actual behaviour
85-
- conversion requires unplanned production behaviour changes
86-
- failures cannot be resolved in scope
87-
- only pipeline-phase grouping (Inventory/Export/Import/Validate style) is available and business-focused grouping cannot be established
88-
- missing-step intent cannot be inferred with enough confidence to create a deterministic behaviour test
89-
- any scenario in `00-scenario-test-inventory.md` remains `unmatched` after conversion
90-
- converted tests are missing required tags or have non-compliant tags
73+
- a scenario's intent is genuinely ambiguous and cannot be resolved by reading the feature file, the DSL design, and the production code together — ask for clarification rather than guessing
74+
- converted tests are missing required tags or have non-compliant tags (fix the tags, then continue)

0 commit comments

Comments
 (0)