.github/skills/csharp-spector-coverage-gaps/SKILL.md
Discovers and implements gaps in Spector test coverage for the C# HTTP client emitter. Use when asked to find missing Spector scenarios, add Spector test coverage, or implement a specific Spector spec for the C# emitter.
npx skillsauth add microsoft/typespec csharp-spector-coverage-gapsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill discovers which Spector scenarios the C# emitter (@typespec/http-client-csharp) does not yet cover, then implements the missing test(s). Spector scenarios are defined in @typespec/http-specs and @azure-tools/azure-http-specs. The coverage dashboard is at https://typespec.io/can-i-use/http/.
Note:
{PKG}refers to<repo-root>/packages/http-client-csharpthroughout this document.
You may receive one of:
packages/http-specs/specs/... or packages/azure-http-specs/specs/....http/encode/duration, http/type/model/flatten, http/type/union/discriminated.{PKG}/generator/TestProjects/Spector.Tests/Http/.{PKG}/generator/TestProjects/Spector.Tests/TestProjects.Spector.Tests.csproj if a new project reference is needed.Generate.ps1).-Stubbed $false, or use Test-Spector.ps1)Test-Spector.ps1 -filter "<spec-path>"Before starting, ensure the build environment is ready. These instructions supersede the repo-root pnpm instructions.
{PKG}):
cd {PKG}
npm ci
npm run build
⚠️ Do NOT run
pnpm installorpnpm buildat the repo root — only the http-client-csharp package build is needed.
The file {PKG}/eng/scripts/Spector-Helper.psm1 defines which specs are included/excluded:
Failing specs are defined in {PKG}/eng/scripts/Spector-Helper.psm1 in the $failingSpecs array. Always check that file for the current list — do not hardcode specs here.
Azure allow-list (only these Azure specs are tested):
http/client/structure/client-operation-grouphttp/client/structure/defaulthttp/client/structure/multi-clienthttp/client/structure/renamed-operationhttp/client/structure/two-operation-grouphttp/resiliency/srv-drivenAll other specs from @typespec/http-specs are included by default.
Compare available specs against existing tests. Run this from {PKG}:
Import-Module "{PKG}/eng/scripts/Spector-Helper.psm1" -DisableNameChecking -Force
# Get all valid specs
$specs = Get-Sorted-Specs | ForEach-Object { Get-SubPath $_ }
# Get all test files
$testFiles = Get-ChildItem -Path "{PKG}/generator/TestProjects/Spector.Tests/Http" -Recurse -Filter "*Tests.cs" |
ForEach-Object { $_.FullName }
# For each spec, check if a corresponding test directory/file exists
foreach ($spec in $specs) {
$specParts = $spec -replace '/', '\' -split '\\'
# Convert to expected test path segments
$testPath = "{PKG}/generator/TestProjects/Spector.Tests/Http"
# ... check if test exists
}
Alternatively, manually compare the list of specs against existing test files:
Existing test coverage (test directories under Spector.Tests/Http/):
Authentication/ApiKey, Authentication/Http/Custom, Authentication/OAuth2, Authentication/Union
Client/Naming, Client/Structure/{ClientOperationGroup,Default,MultiClient,RenamedOperation,TwoOperationGroup}
Documentation
Encode/{Array,Bytes,DateTime,Numeric}
Parameters/{Basic,BodyOptionality,CollectionFormat,Path,Query,Spread}
Payload/{ContentNegotiation,JsonMergePatch,MediaType,Multipart,Pageable,Xml}
Resiliency/SrvDriven/{V1,V2}
Response/StatusCodeRange
Routes
Serialization/EncodedName/Json
Server/{Endpoint/NotDefined,Path/Multiple,Path/Single,Versions/NotVersioned,Versions/Versioned}
SpecialHeaders/{ConditionalRequest,Repeatability}
Versioning/{Added,MadeOptional,Removed,RenamedFrom,ReturnTypeChangedFrom,TypeChangedFrom}
_Type/{Dictionary,Model/Empty,Model/Inheritance/*,Model/Usage,Model/Visibility,Property/*,Scalar,Union,_Array,_Enum/*}
The naive approach of matching spec paths to test directory names can produce false positives because:
type/ → _Type/, array → _Array, enum → _Enum)content-negotiation → ContentNegotiation)UnionTests.cs covers type/union but NOT type/union/discriminated)To find real gaps, verify each candidate by checking whether a test directory exists for the spec's exact path, including sub-paths. A test file at a parent level does NOT cover child specs.
Known failing specs are tracked in $failingSpecs in {PKG}/eng/scripts/Spector-Helper.psm1. Always read that file for the current list.
Important: The gap list evolves over time. Always re-run the comparison to get current gaps. All committed Spector libraries are stubbed —
[SpectorTest]will auto-skip tests unless you regenerate with-Stubbed $falseor useTest-Spector.ps1.
All Spector libraries committed to the repository are stubbed. Generate.ps1 defaults to $Stubbed = $true, which passes --option @typespec/http-client-csharp.generator-name=StubLibraryGenerator to the emitter. Stubbed clients use expression-bodied constructors (=>) instead of block bodies ({ }), and the [SpectorTest] attribute automatically skips tests for stubbed clients.
To generate unstubbed code (for local testing), pass -Stubbed $false:
pwsh eng/scripts/Generate.ps1 -filter "<spec-path>" -Stubbed $false
However, the recommended way to test is via Test-Spector.ps1 (see Step 7), which handles the unstubbed regeneration, test execution, and directory restoration automatically.
Spec files live in {PKG}/node_modules/@typespec/http-specs/specs/ (or @azure-tools/azure-http-specs/specs/ for Azure specs).
If node_modules is not installed, read from the source at <repo-root>/packages/http-specs/specs/.
Each spec contains:
main.tsp — the TypeSpec definition with @scenario and @scenarioDoc decoratorsclient.tsp (optional) — client-level customizations; takes priority over main.tsp during generationtspconfig.yaml (optional in the Spector test project) — C#-specific generation optionsRead the @scenarioDoc decorators to understand:
Use Generate.ps1 with a filter to generate only the specific spec. By default, Generate.ps1 generates stubbed code (same as what is committed to the repo). To generate unstubbed code for local testing, pass -Stubbed $false:
cd {PKG}
# Generate unstubbed (for local testing/development)
pwsh eng/scripts/Generate.ps1 -filter "<spec-path>" -Stubbed $false
# Generate stubbed (default, matches what is committed to repo)
pwsh eng/scripts/Generate.ps1 -filter "<spec-path>"
Examples:
# Single spec (unstubbed for testing)
pwsh eng/scripts/Generate.ps1 -filter "http/encode/duration" -Stubbed $false
# Versioning spec (generates v1 + v2 automatically)
pwsh eng/scripts/Generate.ps1 -filter "http/versioning/added" -Stubbed $false
The generated code lands in {PKG}/generator/TestProjects/Spector/<spec-path>/src/Generated/.
# Check that client code was generated
Get-ChildItem "{PKG}/generator/TestProjects/Spector/<spec-path>/src/Generated/" -Filter "*Client.cs"
Note: All committed Spector libraries are stubbed. The
[SpectorTest]attribute automatically skips tests for stubbed clients. UseTest-Spector.ps1(Step 7) to regenerate unstubbed, run tests, and restore automatically.
Browse the generated code to understand:
*Client.cs — the entry point(s)Get*Client() methodsGetAsync(), PutAsync(body), SendAsync()Models/ — request/response shapesnew XClient(Uri endpoint, XClientOptions options) or new XClient(Uri endpoint, KeyCredential credential, XClientOptions options)Pay attention to:
ClientResult, ClientResult<T>, AsyncPageable<T>)The test directory structure mirrors the spec path with these transformations:
http/ → Http/content-negotiation → ContentNegotiation)type/ → _Type/ (leading underscore because Type is a C# keyword)array → _Array (same reason)enum → _Enum (same reason)Namespace pattern: TestProjects.Spector.Tests.Http.<PascalCasePath>
Example mappings:
| Spec path | Test directory | Namespace |
|-----------|---------------|-----------|
| http/encode/duration | Http/Encode/Duration/ | TestProjects.Spector.Tests.Http.Encode.Duration |
| http/type/union/discriminated | Http/_Type/Union/Discriminated/ | TestProjects.Spector.Tests.Http._Type.Union.Discriminated |
| http/special-words | Http/SpecialWords/ | TestProjects.Spector.Tests.Http.SpecialWords |
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Threading.Tasks;
using <GeneratedNamespace>;
using NUnit.Framework;
namespace TestProjects.Spector.Tests.Http.<Category>.<SubCategory>
{
public class <Name>Tests : SpectorTestBase
{
[SpectorTest]
public Task <ScenarioName>() => Test(async (host) =>
{
var response = await new <Client>(host, null).<Method>Async(<args>);
Assert.AreEqual(<expectedStatusCode>, response.GetRawResponse().Status);
});
}
}
Simple void operation (204 response):
[SpectorTest]
public Task SimpleOp() => Test(async (host) =>
{
var response = await new MyClient(host, null).DoThingAsync();
Assert.AreEqual(204, response.GetRawResponse().Status);
});
GET with typed response:
[SpectorTest]
public Task GetValue() => Test(async (host) =>
{
var response = await new MyClient(host, null).GetValueAsync();
Assert.AreEqual("expected", response.Value);
});
GET with model response:
[SpectorTest]
public Task GetModel() => Test(async (host) =>
{
var response = await new MyClient(host, null).GetModelAsync();
Assert.AreEqual("name", response.Value.Name);
Assert.AreEqual(42, response.Value.Age);
});
PUT/POST with body:
[SpectorTest]
public Task SendModel() => Test(async (host) =>
{
var body = new MyModel("name", 42);
var response = await new MyClient(host, null).SendAsync(body);
Assert.AreEqual(204, response.GetRawResponse().Status);
});
Round-trip (GET then PUT):
[SpectorTest]
public Task RoundTrip() => Test(async (host) =>
{
var client = new MyClient(host, null);
var getResult = await client.GetAsync();
var response = await client.PutAsync(getResult.Value);
Assert.AreEqual(204, response.GetRawResponse().Status);
});
Error assertion:
[SpectorTest]
public Task InvalidKey() => Test((host) =>
{
var exception = Assert.ThrowsAsync<ClientResultException>(
() => new MyClient(host, new ApiKeyCredential("invalid"), null).CallAsync());
Assert.AreEqual(403, exception!.Status);
return Task.CompletedTask;
});
Sub-client access:
[SpectorTest]
public Task SubClientOp() => Test(async (host) =>
{
var response = await new MyClient(host, null)
.GetSubClient()
.DoThingAsync();
Assert.AreEqual(204, response.GetRawResponse().Status);
});
Pagination:
[SpectorTest]
public Task ListItems() => Test(async (host) =>
{
var items = new MyClient(host, null).GetItemsAsync();
int count = 0;
await foreach (var item in items)
{
count++;
}
Assert.Greater(count, 0);
});
using System;
using System.ClientModel; // ClientResult, ClientResultException, ApiKeyCredential
using System.IO; // For file/stream scenarios
using NUnit.Framework; // Test framework
If a new Spector test project directory was created (new spec), a project reference may be needed in TestProjects.Spector.Tests.csproj:
<ProjectReference Include="..\Spector\http\<spec-path>\src\<ProjectName>.csproj" />
Check existing references to match the pattern. The project name typically matches the package-name from tspconfig.yaml or is derived from the namespace.
Only add a project reference if one doesn't already exist for the spec.
The Test-Spector.ps1 script is the recommended way to test Spector specs. It automatically:
cd {PKG}
# Test a specific spec
pwsh eng/scripts/Test-Spector.ps1 -filter "<spec-path>"
Example:
pwsh eng/scripts/Test-Spector.ps1 -filter "http/encode/duration"
pwsh eng/scripts/Get-Spector-Coverage.ps1
If you need more control, you can manually generate unstubbed, build, and run tests:
cd {PKG}
# Generate unstubbed
pwsh eng/scripts/Generate.ps1 -filter "<spec-path>" -Stubbed $false
# Build
dotnet build generator
# Run only your new tests
dotnet test generator/TestProjects/Spector.Tests/TestProjects.Spector.Tests.csproj `
--filter "FullyQualifiedName~TestProjects.Spector.Tests.Http.<YourNamespace>"
# Restore the directory to stubbed state when done
git clean -xfd generator/TestProjects/Spector/<spec-path>
git restore generator/TestProjects/Spector/<spec-path>
Versioning specs generate two clients (v1 and v2). Tests go in separate subdirectories:
Http/Versioning/<Name>/V1/<Name>V1Tests.cs
Http/Versioning/<Name>/V2/<Name>V2Tests.cs
Generation is handled automatically by Generate.ps1 when the path contains versioning.
Similar to versioning — generates v1 and v2 clients from old.tsp and main.tsp.
Some specs have a tspconfig.yaml in the Spector test project that overrides the package-name. Check {PKG}/generator/TestProjects/Spector/<spec-path>/tspconfig.yaml before importing the generated namespace.
Spector-Helper.psm1 to remove items from the failing list unless you're sure the generator now supports them.Generate.ps1 defaults to $Stubbed = $true. Use Test-Spector.ps1 to temporarily regenerate unstubbed and run tests.Test-Spector.ps1 regenerates them unstubbed..cs), .csproj changes, and tspconfig.yaml if needed.<Feature>Tests.cs in the matching directory.[SpectorTest] attribute (not [Test]) for all Spector tests — it enables auto-skip for stubbed implementations.SpectorTestBase.tools
Use when work should span one or more detached tasks but still behave like one job with a single owner context. TaskFlow is the durable flow substrate under authoring layers like Lobster, ACPX, plugins, or plain code. Keep conditional logic in the caller; use TaskFlow for flow identity, child-task linkage, waiting state, revision-checked mutations, and user-facing emergence.
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
# Lobster Lobster executes multi-step workflows with approval checkpoints. Use it when: - User wants a repeatable automation (triage, monitor, sync) - Actions need human approval before executing (send, post, delete) - Multiple tool calls should run as one deterministic operation ## When to use Lobster | User intent | Use Lobster? | | ------------------------------------------------------ | --------------------------
tools
A CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.