diff --git a/.cursor/rules/csharp-style.md b/.cursor/rules/csharp-style.md index 531af57..e92c436 100644 --- a/.cursor/rules/csharp-style.md +++ b/.cursor/rules/csharp-style.md @@ -95,6 +95,8 @@ public async Task PostMove_ShouldReturnBadRequest_WhenSchemaVersionIsWrong() { - **Act:** invoke the single behavior under test. For integration tests, the Act block may include a short sequence of calls if that sequence *is* the behavior (e.g. POST then GET to verify persistence)—keep it one clear scenario per test. - **Assert:** all expectations (`Assert.*`, etc.); read response bodies here when the read is for verification, not for driving the next call (otherwise fold those reads into Act). - Multi-scenario files stay readable with **one AAA triple per `[Fact]` / `[Theory]`** method. +- **Write-time default:** use the workspace VS Code snippet prefix **`xut`** (method) or **`xutc`** (class) from `.vscode/csharp.code-snippets` when authoring tests so AAA sections are present from the first draft. +- **Template reference:** `server/NeonSprawl.Server.Tests/TestTemplates/AAA_TEST_METHOD_TEMPLATE.cs.txt`. ## Tooling diff --git a/.cursor/rules/testing-expectations.md b/.cursor/rules/testing-expectations.md index 673551e..9a0bf01 100644 --- a/.cursor/rules/testing-expectations.md +++ b/.cursor/rules/testing-expectations.md @@ -10,6 +10,7 @@ alwaysApply: true - **Non-trivial logic** (validation, simulation rules, serialization boundaries, idempotency, security-sensitive paths) should have **automated tests** once a test project exists in the repo. - **Default framework:** **xUnit** for new test projects unless the repo already standardizes on something else—stay consistent with existing `*.Tests` projects. - **Layout:** use **Arrange / Act / Assert** in every test method; see [C# style — Unit and integration tests](csharp-style.md#unit-and-integration-tests-arrange-act-assert). +- **At-write-time default:** start new tests with snippet **`xut`** / **`xutc`** from `.vscode/csharp.code-snippets` (AAA sections pre-inserted) or copy `server/NeonSprawl.Server.Tests/TestTemplates/AAA_TEST_METHOD_TEMPLATE.cs.txt`. - **Names:** **`MethodName_ShouldExpectedOutcome_WhenScenario`**; see [C# style — Test method naming convention](csharp-style.md#test-method-naming-convention). - **No test project yet:** It is acceptable to ship a small change without a suite only while the server is mostly scaffolding. When you add the **first** testable module, **add a `NeonSprawl.Server.Tests` (or equivalent) project** and start covering that area; do not leave new core logic permanently untested. - **Bug fixes:** Prefer a **regression test** when the fix is non-obvious or has broken before. diff --git a/.vscode/csharp.code-snippets b/.vscode/csharp.code-snippets new file mode 100644 index 0000000..106f6f5 --- /dev/null +++ b/.vscode/csharp.code-snippets @@ -0,0 +1,47 @@ +{ + "xUnit Test Method AAA": { + "scope": "csharp", + "prefix": "xut", + "description": "xUnit test method with explicit Arrange/Act/Assert", + "body": [ + "[Fact]", + "public async Task ${1:MethodName}_Should${2:ExpectedOutcome}_When${3:Scenario}()", + "{", + " // Arrange", + " ${4:// setup}", + "", + " // Act", + " ${5:// execute}", + "", + " // Assert", + " ${6:// verify}", + "}" + ] + }, + "xUnit Test Class AAA": { + "scope": "csharp", + "prefix": "xutc", + "description": "xUnit test class scaffold with AAA guidance", + "body": [ + "using Xunit;", + "", + "namespace ${1:NeonSprawl.Server.Tests};", + "", + "public sealed class ${2:Feature}Tests", + "{", + " [Fact]", + " public async Task ${3:MethodName}_Should${4:ExpectedOutcome}_When${5:Scenario}()", + " {", + " // Arrange", + " ${6:// setup}", + "", + " // Act", + " ${7:// execute}", + "", + " // Assert", + " ${8:// verify}", + " }", + "}" + ] + } +} diff --git a/scripts/git-hooks/pre-commit b/scripts/git-hooks/pre-commit index 9014a4e..ff710bd 100755 --- a/scripts/git-hooks/pre-commit +++ b/scripts/git-hooks/pre-commit @@ -43,18 +43,3 @@ if has_staged_match '^client/scripts/.*\.gd$'; then exit 1 fi fi - -# Enforce explicit AAA markers in changed C# test files. -for f in "${staged_files[@]}"; do - if [[ ! "$f" =~ ^server/NeonSprawl\.Server\.Tests/.*Tests\.cs$ ]]; then - continue - fi - if [[ ! -f "$f" ]]; then - continue - fi - if ! rg -q 'Arrange' "$f" || ! rg -q 'Act' "$f" || ! rg -q 'Assert' "$f"; then - echo "pre-commit: $f is missing explicit AAA markers (Arrange/Act/Assert)." >&2 - echo "Add clear Arrange/Act/Assert sections in changed C# tests." >&2 - exit 1 - fi -done diff --git a/server/NeonSprawl.Server.Tests/TestTemplates/AAA_TEST_METHOD_TEMPLATE.cs.txt b/server/NeonSprawl.Server.Tests/TestTemplates/AAA_TEST_METHOD_TEMPLATE.cs.txt new file mode 100644 index 0000000..34032f1 --- /dev/null +++ b/server/NeonSprawl.Server.Tests/TestTemplates/AAA_TEST_METHOD_TEMPLATE.cs.txt @@ -0,0 +1,12 @@ +[Fact] +public async Task MethodName_ShouldExpectedOutcome_WhenScenario() +{ + // Arrange + // Create factory/client/dependencies and test data. + + // Act + // Invoke exactly one behavior/scenario under test. + + // Assert + // Verify outcome(s) with Assert.* calls. +}