Skip to content

Development, testing & publishing

How to build the .NET solution, run the tests with coverage, and publish the NuGet packages via trusted publishing (OIDC — no stored API key).

Solution layout

src/
  FdaObservability.Core   net8.0    shared query client (FdaKustoClient, models, auth)   → NuGet: FdaObservability.Core
  FdaObservability.App    net8.0-windows  WinForms review app
  FdaObservability.Api    net8.0    REST API + OpenAPI
  FdaObservability.Mcp    net8.0    MCP stdio server
  FdaObservability.Cli    net8.0    fda-obs CLI                                            → NuGet: FdaObservability.Cli
tests/
  FdaObservability.Tests  net8.0    xUnit tests + coverage

The App targets net8.0-windows, so the full solution builds on Windows. The cross-platform projects (Core, Api, Mcp, Cli, Tests) build and test on any OS.

Build & test

dotnet restore src/FdaObservability.sln
dotnet build   src/FdaObservability.sln -c Release
dotnet test    src/FdaObservability.sln -c Release --collect:"XPlat Code Coverage"

CI runs the same on windows-latest (.github/workflows/dotnet.yml) and uploads the Cobertura coverage report as an artifact.

Coverage

The suite focuses on the deterministic logic and the surface behaviour:

Assembly What's covered
Core KQL builders + literal escaping, time-window resolution, row mapping (via DataTableReader), KPI math, options validation, token-scope derivation, and the client's query methods (through an injected fake executor). The thin network executor is [ExcludeFromCodeCoverage].
Api Every endpoint via WebApplicationFactory with a fake IFdaKustoClient — routing, query binding, 404, health 200/503, the API-key gate, and the swagger redirect.
Mcp The full request handler (initialize, tools/list, tools/call, ping, errors, notifications) and the stdio read loop, against a fake client.
Cli The argument parser and every command path (JSON + table output, not-found, unknown command, errors).

The only uncovered lines are the process entry points (Program env/auth wiring and the stdio loop's host plumbing), which are exercised by manual smoke tests rather than unit tests.

NuGet packages

Two projects are packable:

Package Project Install
FdaObservability.Core shared library dotnet add package FdaObservability.Core
FdaObservability.Cli fda-obs global tool dotnet tool install -g FdaObservability.Cli

Pack locally:

dotnet pack src/FdaObservability.Core/FdaObservability.Core.csproj -c Release -o artifacts
dotnet pack src/FdaObservability.Cli/FdaObservability.Cli.csproj  -c Release -o artifacts

Trusted publishing (OIDC)

Publishing uses NuGet trusted publishing: the publish workflow requests a short-lived OIDC token from GitHub, exchanges it with nuget.org for a temporary API key (valid 1 hour), and pushes. No long-lived API key is stored.

flowchart LR
    gha[GitHub Actions job<br/>id-token: write] -->|OIDC token| nuget[nuget.org<br/>token exchange]
    nuget -->|temp API key 1h| gha
    gha -->|dotnet nuget push| feed[(nuget.org feed)]

One-time setup (repo owner)

  1. nuget.org → your username → Trusted Publishing → Add policy:
    • Repository Owner: PatrickGallucci
    • Repository: fda-observability
    • Workflow File: nuget-publish.yml (file name only — no path)
    • Environment: leave empty
  2. Add a repository secret NUGET_USER = your nuget.org profile name (not your email).
  3. If the package IDs don't exist yet, the first successful publish reserves them under your account.

Pending activation

For private repos the policy is temporarily active for 7 days until the first publish locks it to the repo/owner IDs. Publish within that window to make it permanent.

Publishing a release

The workflow triggers on a published GitHub Release (or manual Run workflow):

  1. Bump <Version> in Core/Cli csproj (and the others) + update CHANGELOG.md.
  2. Commit, tag vX.Y.Z, and create a GitHub Release for the tag.
  3. The publish workflow packs both projects (versioned from the tag) and pushes them via OIDC.

Manual dispatch accepts an optional version input; otherwise the csproj version is used.