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 skills add https://github.com/microsoft/typespec --skill csharp-spector-coverage-gaps
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-csharp throughout 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 install or pnpm build at 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 $false or use Test-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. Use Test-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.Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when implementing any feature or bugfix, before writing implementation code
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Expert guidance for systematic backtesting of trading strategies. Use when developing, testing, stress-testing, or validating quantitative trading strategies. Covers "beating ideas to death" methodology, parameter robustness testing, slippage modeling, bias prevention, and interpreting backtest results. Applicable when user asks about backtesting, strategy validation, robustness testing, avoiding overfitting, or systematic trading development.
Cloud laboratory platform for automated protein testing and validation. Use when designing proteins and needing experimental validation including binding assays, expression testing, thermostability measurements, enzyme activity assays, or protein sequence optimization. Also use for submitting experiments via API, tracking experiment status, downloading results, optimizing protein sequences for better expression using computational tools (NetSolP, SoluProt, SolubleMPNN, ESM), or managing protein design workflows with wet-lab validation.
This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
Take microsoft/csharp-spector-coverage-gaps from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.