Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 29 additions & 20 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
name: .NET Core
name: .NET

on:
push:
branches: [ main ]
paths-ignore:
- 'README.md'
paths-ignore:
- '**.md'
- '**.jpg'

- '**.png'

pull_request:
branches: [ main ]

Expand All @@ -15,30 +15,39 @@ jobs:

runs-on: ${{ matrix.os }}
strategy:
matrix:
matrix:
os: [ ubuntu-latest ]
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
- uses: actions/checkout@v4
- name: Setup .NET 8.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: 3.1.301
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
- name: Test
run: dotnet test --no-restore --verbosity normal
- name: Pack
- name: Pack SmokeMe
if: matrix.os == 'ubuntu-latest'
run: |
arrTag=(${GITHUB_REF//\// })
VERSION="${arrTag[2]}"
VERSION="${VERSION//v}"
dotnet pack --configuration Release
- name: Upload Artifact
run: dotnet pack SmokeMe/SmokeMe.csproj --configuration Release --no-build
- name: Pack SmokeMe.AspNetCore
if: matrix.os == 'ubuntu-latest'
run: dotnet pack SmokeMe.AspNetCore/SmokeMe.AspNetCore.csproj --configuration Release --no-build
- name: Upload SmokeMe NuGet
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v4
with:
name: smokeme-nupkg
path: SmokeMe/bin/Release/*.nupkg
- name: Upload SmokeMe.AspNetCore NuGet
if: matrix.os == 'ubuntu-latest'
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: nupkg
path: /home/runner/work/SmokeMe/SmokeMe/SmokeMe/bin/Release/*.nupkg
name: smokeme-aspnetcore-nupkg
path: SmokeMe.AspNetCore/bin/Release/*.nupkg
94 changes: 94 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**SmokeMe** is a convention-based .NET library (v3.0.0) split into two NuGet packages:
- **SmokeMe** (core, netstandard2.0) — framework-agnostic: reflection discovery, execution, reporting. Zero ASP.NET dependency.
- **SmokeMe.AspNetCore** (net8.0/net9.0/net10.0) — ASP.NET Core integration via `AddSmokeMe()` + `MapSmokeEndpoint()`.

Author: Thomas PIERRAIN (42 skillz).

## Build & Test Commands

```bash
# Restore + build
dotnet build SmokeMe.sln

# Run all tests
dotnet test SmokeMe.sln

# Run a single test by name
dotnet test SmokeMe.Tests/SmokeMe.Tests.csproj --filter "FullyQualifiedName~SmokeControllerShould.Return_OK_200"

# Pack both NuGet packages
dotnet pack SmokeMe/SmokeMe.csproj --configuration Release
dotnet pack SmokeMe.AspNetCore/SmokeMe.AspNetCore.csproj --configuration Release
```

## Architecture

### Core Flow

```
SmokeTestAutoFinder (reflection discovery + DI instantiation)
MapSmokeEndpoint (GET /smoke?categories=X) ──► SmokeTestRunner (parallel TPL execution with global timeout)
│ │
▼ ▼
SmokeTestSessionResultAdapter ◄──── SmokeTestsSessionReport / TimeoutSmokeTestsSessionReport
SmokeTestsSessionReportDto (HTTP response)
```

### Key Types

**SmokeMe (core):**
- **`SmokeTest`** — Abstract base class. Consumers implement `SmokeTestName`, `Description`, `Scenario()`. Optional: `HasToBeDiscarded()` for feature-toggle integration.
- **`ISmokeTestConfiguration`** — Abstraction for timeout + enabled flag. Decoupled from ASP.NET's `IConfiguration`.
- **`SmokeMeOptions`** — Default implementation of `ISmokeTestConfiguration`.
- **`SmokeTestAutoFinder`** (`IFindSmokeTests`) — Scans all loaded assemblies for `SmokeTest` subclasses, filters by `[Category]`/`[Ignore]`, instantiates via `IServiceProvider`.
- **`SmokeTestRunner`** — Static `ExecuteAllSmokeTestsInParallel()`. Uses `Task.WhenAny` for global timeout enforcement.

**SmokeMe.AspNetCore:**
- **`SmokeMeServiceCollectionExtensions.AddSmokeMe()`** — Registers `ISmokeTestConfiguration` + `IFindSmokeTests` in DI.
- **`SmokeMeEndpointRouteBuilderExtensions.MapSmokeEndpoint()`** — Minimal API endpoint. HTTP status codes: 200 (all passed), 500 (failures), 504 (timeout), 501 (no tests found), 503 (disabled via config).
- **`SmokeMeConfigurationAdapter`** — Bridges ASP.NET `IConfiguration` to `ISmokeTestConfiguration`.
- **`SmokeController`** — Legacy MVC controller (marked `[Obsolete]`, will be removed in v4).

### Configuration Keys (appsettings.json)

```json
{
"Smoke": {
"GlobalTimeoutInMsec": 30000,
"IsSmokeTestExecutionEnabled": true
}
}
```

Defaults defined in `Constants.cs`.

## Solution Structure

| Project | Target | Purpose |
|---------|--------|---------|
| `SmokeMe/` | netstandard2.0 | Core library (NuGet package) |
| `SmokeMe.AspNetCore/` | net8.0;net9.0;net10.0 | ASP.NET Core integration (NuGet package) |
| `SmokeMe.Tests/` | net10.0 | Tests (NUnit + NFluent + NSubstitute + Diverse) |
| `Samples/Sample.Api/` | net10.0 | Example API using `AddSmokeMe()` + `MapSmokeEndpoint()` |
| `Samples/Sample.ExternalSmokeTests/` | netstandard2.0 | Smoke tests in a separate assembly |

## Testing Conventions

- **NUnit** framework, **NFluent** assertions (`Check.That(...)`), **NSubstitute** for mocking, **Diverse** (`Fuzzer`) for test data generation.
- Acceptance tests dominate — they exercise `SmokeController` end-to-end with stubbed dependencies.
- Test fixtures use a **`Stub`** helper class (in `SmokeMe.Tests/Helpers/Stub.cs`) to build `ISmokeTestConfiguration`, `IConfiguration` and `IFindSmokeTests` instances.
- Tests are fast (no I/O), isolated (no shared `[SetUp]` state).

## CI

GitHub Actions (`.github/workflows/dotnet.yml`): builds on Ubuntu with .NET 8 + 9, runs tests, creates both NuGet packages on push to main.
Binary file added Images/Bluesky_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed Images/Twitter_icon.gif
Binary file not shown.
Binary file added Images/breaking-news-v3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed Images/breakingChanges.jpg
Binary file not shown.
172 changes: 172 additions & 0 deletions MIGRATION-v2-to-v3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Migration Guide: SmokeMe v2 to v3

## Why v3?

SmokeMe v2 was a single NuGet package targeting `netcoreapp3.1` (EOL since December 2022). It embedded an MVC controller, Swagger/OpenAPI dependencies, Newtonsoft.Json, and API versioning — all baked in, whether you needed them or not.

v3 fixes this by:

1. **Splitting into two packages** — a framework-agnostic core and an ASP.NET Core integration layer
2. **Targeting supported runtimes** — `netstandard2.0` (core) + `net8.0`/`net9.0` (ASP.NET integration)
3. **Removing heavy dependencies** — no more Newtonsoft.Json, Swashbuckle, or API versioning pulled in automatically
4. **Making registration explicit** — you opt-in with `AddSmokeMe()` + `MapSmokeEndpoint()` instead of relying on convention-based controller discovery

## What changed

| Area | v2 | v3 |
|------|----|----|
| **NuGet packages** | 1 package: `SmokeMe` | 2 packages: `SmokeMe` (core) + `SmokeMe.AspNetCore` (integration) |
| **Target framework** | `netcoreapp3.1` | `netstandard2.0` (core) + `net8.0`/`net9.0` (ASP.NET) |
| **Registration** | Automatic (MVC controller discovered by convention) | Explicit: `AddSmokeMe()` + `MapSmokeEndpoint()` |
| **Endpoint style** | MVC Controller (`SmokeController`) | Minimal API endpoint (via `MapSmokeEndpoint()`) |
| **JSON serializer** | Newtonsoft.Json | System.Text.Json |
| **Embedded dependencies** | Swashbuckle, API Versioning, Newtonsoft.Json | None — only `System.Text.Json` (core) and `Microsoft.AspNetCore.App` framework reference |
| **Configuration** | `IConfiguration` extension methods (`GetSmokeMeGlobalTimeout()`, `IsSmokeTestExecutionEnabled()`) | `ISmokeTestConfiguration` interface (auto-bridged from `appsettings.json`) |
| **`ICheckSmoke` interface** | Present (deprecated since v2) | Removed |

## Step-by-step migration

### 1. Update NuGet references

Remove the old `SmokeMe` package and install both v3 packages:

```xml
<!-- Remove -->
<PackageReference Include="SmokeMe" Version="2.x.x" />

<!-- Add -->
<PackageReference Include="SmokeMe" Version="3.0.0" />
<PackageReference Include="SmokeMe.AspNetCore" Version="3.0.0" />
```

> If you have smoke tests in a **separate assembly** (class library), that assembly only needs the `SmokeMe` package (core). Only your **web host** project needs `SmokeMe.AspNetCore`.

### 2. Register services and map the endpoint

**v2** — no registration needed (the `SmokeController` was discovered automatically by MVC):

```csharp
// Startup.cs — v2: nothing special, the controller just worked
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
// ...
}
```

**v3** — explicit registration required:

```csharp
// Program.cs (Minimal API style)
using SmokeMe.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSmokeMe(); // registers smoke test discovery + configuration

var app = builder.Build();

app.MapSmokeEndpoint(); // GET /smoke (default path)

app.Run();
```

Or if you use a custom path:

```csharp
app.MapSmokeEndpoint("/healthcheck/smoke");
```

### 3. Remove `ICheckSmoke` usages (if any remain)

`ICheckSmoke` was deprecated in v2 and has been **removed** in v3. If you still have types implementing it:

```csharp
// v2 (deprecated)
public class MyTest : ICheckSmoke { ... }

// v3
public class MyTest : SmokeTest
{
public override string SmokeTestName => "My test";
public override string Description => "Checks something";
public override async Task<SmokeTestResult> Scenario() { ... }
}
```

### 4. Update configuration access (if you used extension methods)

v2 provided extension methods on `IConfiguration`:

```csharp
// v2
var timeout = configuration.GetSmokeMeGlobalTimeout();
var isEnabled = configuration.IsSmokeTestExecutionEnabled();
```

These extension methods have been **removed**. In v3, configuration is read automatically from `appsettings.json` by the `AddSmokeMe()` registration. The configuration keys are unchanged:

```json
{
"Smoke": {
"GlobalTimeoutInMsec": 30000,
"IsSmokeTestExecutionEnabled": true
}
}
```

If you need programmatic configuration:

```csharp
builder.Services.AddSmokeMe(options =>
{
options.GlobalTimeout = TimeSpan.FromSeconds(60);
options.IsExecutionEnabled = true;
});
```

`appsettings.json` values take precedence over programmatic defaults when both are present.

### 5. Handle JSON serialization differences

v3 uses **System.Text.Json** instead of Newtonsoft.Json. This matters if you were relying on specific Newtonsoft behaviors in your smoke test responses:

- Property names are now **camelCase by default** (System.Text.Json default) instead of the Newtonsoft default
- If you were parsing the `/smoke` response in external tools, verify the JSON shape still matches your expectations

### 6. Remove Swagger/API versioning workarounds (if any)

v2 pulled in Swashbuckle and `Microsoft.AspNetCore.Mvc.Versioning`. If you had added configurations or workarounds because of these transitive dependencies, you can safely remove them. v3 has no opinion on Swagger or API versioning.

## What about the legacy `SmokeController`?

The `SmokeMe.AspNetCore` package still ships a `SmokeController` marked `[Obsolete]`. This is a **temporary migration aid**:

- If you call `AddSmokeMe()` + `MapSmokeEndpoint()`, the Minimal API endpoint is used (recommended)
- If you still use `AddControllers()` and MVC routing, the legacy `SmokeController` will also be discovered — both can coexist
- The `SmokeController` will be **removed in v4**

**Recommendation:** migrate to `MapSmokeEndpoint()` now. If you see an `[Obsolete]` compiler warning about `SmokeController`, it means something is still referencing it.

## Smoke test classes: no changes needed

The `SmokeTest` abstract base class is **unchanged**. Your existing smoke test implementations (`SmokeTestName`, `Description`, `Scenario()`, `HasToBeDiscarded()`) work as-is. Constructor injection via `IServiceProvider` also works identically.

## Compatibility matrix

| .NET version | SmokeMe (core) | SmokeMe.AspNetCore |
|---|---|---|
| .NET 8 (LTS) | Yes (via netstandard2.0) | Yes |
| .NET 9 | Yes (via netstandard2.0) | Yes |
| .NET Framework 4.6.1+ | Yes (via netstandard2.0) | No (ASP.NET Core only) |
| .NET Core 3.1 / .NET 5-7 | Yes (via netstandard2.0) | No (EOL runtimes) |

## TL;DR — minimal migration checklist

- [ ] Replace `SmokeMe` v2 package with `SmokeMe` + `SmokeMe.AspNetCore` v3
- [ ] Add `builder.Services.AddSmokeMe()` in your service registration
- [ ] Add `app.MapSmokeEndpoint()` in your endpoint configuration
- [ ] Replace any `ICheckSmoke` implementations with `SmokeTest` (if not already done)
- [ ] Remove any calls to `configuration.GetSmokeMeGlobalTimeout()` or `configuration.IsSmokeTestExecutionEnabled()`
- [ ] Verify your `appsettings.json` `Smoke:` section still works (keys unchanged)
- [ ] Run your smoke tests to confirm the `/smoke` endpoint responds correctly
Loading
Loading