diff --git a/src/components/landing/TechStack.astro b/src/components/landing/TechStack.astro index e34b580f..57fac7a1 100644 --- a/src/components/landing/TechStack.astro +++ b/src/components/landing/TechStack.astro @@ -4,16 +4,16 @@ import SectionEyebrow from './SectionEyebrow.astro'; const stack = [ { name: '.NET 10', version: 'C# latest', sub: 'net10.0 LTS', icon: 'lucide:hexagon' }, - { name: 'Aspire', version: '13.3', sub: 'orchestration', icon: 'lucide:layers' }, - { name: 'EF Core', version: '10.0.8', sub: 'PostgreSQL 17', icon: 'lucide:database' }, + { name: 'Aspire', version: '13.6', sub: 'orchestration', icon: 'lucide:layers' }, + { name: 'EF Core', version: '10.0.14', sub: 'PostgreSQL 18', icon: 'lucide:database' }, { name: 'Mediator', version: '3.0', sub: 'source generator', icon: 'lucide:radio' }, - { name: 'FluentValidation', version: '12.1', sub: 'request validation', icon: 'lucide:shield-check' }, + { name: 'FluentValidation', version: '12.2', sub: 'request validation', icon: 'lucide:shield-check' }, { name: 'Hangfire', version: '1.8', sub: 'background jobs', icon: 'lucide:zap' }, - { name: 'Valkey', version: '8', sub: 'distributed cache', icon: 'lucide:server' }, + { name: 'Valkey', version: '9', sub: 'distributed cache', icon: 'lucide:server' }, { name: 'Serilog', version: '4.x', sub: 'structured logs', icon: 'lucide:scroll-text' }, - { name: 'OpenTelemetry', version: '1.15', sub: 'OTLP exporter', icon: 'lucide:activity' }, - { name: 'Scalar', version: '2.14', sub: 'OpenAPI 3.1 browser',icon: 'lucide:book-open' }, - { name: 'Finbuckle', version: '10.0', sub: 'multitenancy', icon: 'lucide:building-2' }, + { name: 'OpenTelemetry', version: '1.20', sub: 'OTLP exporter', icon: 'lucide:activity' }, + { name: 'Scalar', version: '2.18', sub: 'OpenAPI 3.1 browser',icon: 'lucide:book-open' }, + { name: 'Finbuckle', version: '10.1', sub: 'multitenancy', icon: 'lucide:building-2' }, { name: 'React + Vite', version: 'TS 5.7', sub: 'admin · dashboard', icon: 'lucide:code' }, ]; --- diff --git a/src/components/landing/Testing.astro b/src/components/landing/Testing.astro index a3a15a9a..08a41eab 100644 --- a/src/components/landing/Testing.astro +++ b/src/components/landing/Testing.astro @@ -129,7 +129,7 @@ const pillars: Pillar[] = [
$ dotnet test
- ⟳ Testcontainers - postgres:17 · valkey ready
+ ⟳ Testcontainers - postgres:18 · valkey ready
✓ unit ........................ 774 passed
✓ integration (real Postgres) . 661 passed
✓ architecture (NetArchTest) . 49 passed
diff --git a/src/content/docs/building-blocks/mailing.mdx b/src/content/docs/building-blocks/mailing.mdx
index f60eecb9..ccbfeb2c 100644
--- a/src/content/docs/building-blocks/mailing.mdx
+++ b/src/content/docs/building-blocks/mailing.mdx
@@ -1,6 +1,6 @@
---
title: Mailing building block
-lastUpdated: 2026-10-02
+lastUpdated: 2026-11-03
description: SMTP or SendGrid email abstraction behind a single IMailService, with multi-recipient delivery and HTML body support.
sidebar:
label: Mailing
@@ -140,7 +140,7 @@ An unknown name (a typo such as `Plain`) makes the options fail to bind, so the
### Local mail catcher (Docker Compose)
-`deploy/docker/docker-compose.yml` reads the SMTP target from `.env` and ships a [Mailpit](https://mailpit.axllent.org) service (`axllent/mailpit:v1.31.3`) under the `mail-catcher` compose profile. The defaults in `.env.example` enable that profile and point the API at it, so confirmation, password-reset and welcome e-mails work without an SMTP account, and none of them is delivered:
+`deploy/docker/docker-compose.yml` reads the SMTP target from `.env` and ships a [Mailpit](https://mailpit.axllent.org) service (`axllent/mailpit:v1.33.0`) under the `mail-catcher` compose profile. The defaults in `.env.example` enable that profile and point the API at it, so confirmation, password-reset and welcome e-mails work without an SMTP account, and none of them is delivered:
```bash
# .env
diff --git a/src/content/docs/changelog/index.mdx b/src/content/docs/changelog/index.mdx
index 2c2ade8d..e483d6e2 100644
--- a/src/content/docs/changelog/index.mdx
+++ b/src/content/docs/changelog/index.mdx
@@ -13,6 +13,7 @@ Notable changes to the kit, newest first.
## 2026-11-03
+- **Dependencies: the backend stack is on the latest stable versions (chore).** ASP.NET Core, EF Core and `Microsoft.Extensions.*` move to the **10.0.14** patch line (with the `System.Security.Cryptography.Xml` / `.Pkcs` pins), .NET **Aspire 13.6.4** (hosting packages and AppHost SDK), Finbuckle.MultiTenant 10.1.6, FluentValidation 12.2.0, OpenTelemetry 1.20.1 (EF Core and StackExchange.Redis instrumentation on 1.20.1-beta.1), StackExchange.Redis 3.4.31, Scalar.AspNetCore 2.18.7, MailKit/MimeKit 4.18.2, AWSSDK.S3 4.0.104.8, QuestPDF 2026.9.1, Testcontainers 4.16.0 and Spectre.Console.Cli 0.57.2. `global.json` now asks for SDK `10.0.400` or later (still `latestFeature`). Containers: Postgres is **18** everywhere (Docker Compose already was; the Testcontainers harness and the CI migrator smoke move from 17), Valkey 9.1.3, RustFS 1.0.4, Mailpit v1.33.0, `amazon/aws-cli` 2.39.3, and RedisInsight is pinned to 3.8.0 instead of `latest`. Aspire 13.6 still defaults to `postgres:18.3`, so an Aspire data volume from 13.4 or later keeps working; only a volume created before 13.4 needs the [wipe](/docs/getting-started/troubleshooting/). Terraform's AWS provider constraint moves to `~> 6.71` (Terraform 1.15.4 is still the floor). If you forked the CLI, Spectre.Console.Cli 0.57 made `ExecuteAsync` overrides `public`. Part of [#1453](https://github.com/fullstackhero/dotnet-starter-kit/issues/1453); see PR [#1460](https://github.com/fullstackhero/dotnet-starter-kit/pull/1460).
- **Frontends: i18n follow-ups (fix).** The admin language switcher now saves with `If-Match`, so a profile edit made in between is no longer silently overwritten (it retries once on `412`). The dashboard container now honours `FSH_DEFAULT_LANGUAGE` like the admin one; before, a containerized dashboard always started in `en-US`. `deploy/docker` and Terraform pass the setting to both apps. The dashboard no longer matches the English text of the deactivated-tenant error, because the API always sends its `code` now. See [Internationalization](/docs/cross-cutting-concerns/internationalization/#per-deployment-default) and [#1459](https://github.com/fullstackhero/dotnet-starter-kit/pull/1459).
- **Identity: a role can no longer hold the same permission twice (fix, migration).** `identity.RoleClaims` had no uniqueness, so two API instances starting at once after a release that adds a permission, or the tenant seeder overlapping the startup sync, could both insert the same grant and leave a duplicate row behind with nothing in the logs. Authorization was not affected, but the table drifted. The new `AddRoleClaimsUniqueIndex` Identity migration first deletes existing duplicates (keeping the lowest `Id` per `(RoleId, ClaimType, ClaimValue)`) and then adds a unique index on those columns, which replaces the `RoleId`-only index. `RolePermissionSyncer` and `IdentityDbInitializer` treat a unique violation on that index as another writer having inserted the grant first and log it at Information instead of failing, and `PUT /api/v1/identity/{id}/permissions` collapses a permission repeated in the request. Run the DbMigrator `apply` as usual; claims with a `NULL` value are not affected. See [Authorization](/docs/security/authorization/#the-role-permission-syncer), [#1435](https://github.com/fullstackhero/dotnet-starter-kit/issues/1435) and [#1458](https://github.com/fullstackhero/dotnet-starter-kit/pull/1458).
- **Admin & dashboard: sign-in now asks for the authenticator code when two-factor is on (fix).** Both apps let a user enable two-factor authentication under Settings > Security, but neither login page could send the code, so the next sign-in failed with `401 two_factor_required` and locked the user out. When `POST /api/v1/identity/token/issue` answers with the ProblemDetails `code` `Identity.TwoFactorRequired`, the login card now switches to a code step (six digits, `autocomplete="one-time-code"` so the browser or password manager can fill it), then repeats the call with `twoFactorCode`. A wrong code (`Identity.TwoFactorInvalid`) shows an inline error and keeps the user on the step; "Back to sign in" returns to the password step. The password is held only in the login page's memory for the retry, never in browser storage, and is dropped on back, success or leaving the page. The clients branch on the `code` extension, not the localized `detail` text. Recovery codes are still not supported. See [Two-factor authentication](/docs/security/two-factor/#signing-in-from-the-react-apps) and [#1446](https://github.com/fullstackhero/dotnet-starter-kit/issues/1446).
diff --git a/src/content/docs/compare/fsh-vs-abp.mdx b/src/content/docs/compare/fsh-vs-abp.mdx
index cc2c5fe7..49d40629 100644
--- a/src/content/docs/compare/fsh-vs-abp.mdx
+++ b/src/content/docs/compare/fsh-vs-abp.mdx
@@ -1,6 +1,6 @@
---
title: fullstackhero vs ABP Framework
-lastUpdated: 2026-06-11
+lastUpdated: 2026-11-03
description: A fair comparison of fullstackhero and ABP Framework for building production .NET 10 applications. License, architecture, learning curve, and when to pick which.
sidebar:
label: vs ABP Framework
@@ -32,7 +32,7 @@ ABP Framework and fullstackhero both target the same problem - getting a product
| ORM | EF Core 10 (PostgreSQL via Npgsql by default; SQL Server provider available) | EF Core with abstraction layer + LINQ via repository pattern |
| Background jobs | Hangfire 1.8 | ABP's `IBackgroundJobManager` (multiple providers) |
| Realtime | SignalR + Server-Sent Events | SignalR via ABP modules |
-| Observability | Serilog 4 + OpenTelemetry 1.19 (OTLP exporter), pre-wired | OpenTelemetry support; configuration via ABP modules |
+| Observability | Serilog 4 + OpenTelemetry 1.20 (OTLP exporter), pre-wired | OpenTelemetry support; configuration via ABP modules |
| Frontend included | React + Vite admin and dashboard, in the repo | MVC + Razor / Blazor / Angular options - Lepton theme is paid in ABP Commercial |
| CLI scaffolding | `fsh new ` | `abp new ` (Studio offers a GUI) |
| Stars (May 2026) | 6.4k★ | 14.2k★ |
diff --git a/src/content/docs/compare/fsh-vs-blazorplate.mdx b/src/content/docs/compare/fsh-vs-blazorplate.mdx
index c3ab1793..f76951a0 100644
--- a/src/content/docs/compare/fsh-vs-blazorplate.mdx
+++ b/src/content/docs/compare/fsh-vs-blazorplate.mdx
@@ -1,6 +1,6 @@
---
title: fullstackhero vs BlazorPlate
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
description: How fullstackhero compares to BlazorPlate - the difference between a free, open-source .NET 10 starter kit and a paid closed-source SaaS template.
sidebar:
label: vs BlazorPlate
@@ -30,7 +30,7 @@ BlazorPlate is a paid commercial multi-tenant SaaS starter for .NET, sold as a o
| Frontend | React 19 + Vite admin console + tenant dashboard (both in the repo) | Blazor Server + Blazor WebAssembly |
| i18n | Not built-in | 20+ languages, RTL support |
| Background jobs | Hangfire 1.8 | Hangfire |
-| Observability | Serilog 4 + OpenTelemetry 1.19 (OTLP) | Serilog |
+| Observability | Serilog 4 + OpenTelemetry 1.20 (OTLP) | Serilog |
| Realtime | SignalR (Valkey backplane) + Server-Sent Events | SignalR |
| File storage | Tenant-scoped S3 abstraction (RustFS locally) | File storage primitives |
| Email | MailKit / SendGrid | Email service |
diff --git a/src/content/docs/compare/fsh-vs-clean-architecture.mdx b/src/content/docs/compare/fsh-vs-clean-architecture.mdx
index 599d545d..555d015e 100644
--- a/src/content/docs/compare/fsh-vs-clean-architecture.mdx
+++ b/src/content/docs/compare/fsh-vs-clean-architecture.mdx
@@ -1,6 +1,6 @@
---
title: fullstackhero vs Clean Architecture templates
-lastUpdated: 2026-05-19
+lastUpdated: 2026-11-03
description: How fullstackhero compares to the Jason Taylor and Ardalis Clean Architecture templates - the difference between shipping an architecture and shipping production modules on top of one.
sidebar:
label: vs Clean Architecture
@@ -27,7 +27,7 @@ The Clean Architecture templates by Jason Taylor and Steve Smith (Ardalis) are t
| Multitenancy | Not included | Not included | Finbuckle 10 with EF Core global query filter, IGlobalEntity opt-out, tenant-aware cache + jobs + outbox |
| Auditing | Not included | Not included | EF SaveChanges interceptor + per-entity before/after + queryable `/audits` |
| Background jobs | Not included | Not included | Hangfire 1.8 with tenant context preserved |
-| Observability | Console logging | Serilog example | Serilog 4 + OpenTelemetry 1.19 (OTLP) pre-wired |
+| Observability | Console logging | Serilog example | Serilog 4 + OpenTelemetry 1.20 (OTLP) pre-wired |
| Caching | Not included | Not included | HybridCache (in-memory + Valkey) with tenant-scoped invalidation |
| API browser | Swagger | Swagger | Scalar (OpenAPI 3.1) on `/scalar/` |
| Frontend | Angular 21 / React 19 (sample) | None (API-only) | React 19 + Vite admin + dashboard, in the repo |
diff --git a/src/content/docs/deployment/aspire.mdx b/src/content/docs/deployment/aspire.mdx
index 432de280..134edd22 100644
--- a/src/content/docs/deployment/aspire.mdx
+++ b/src/content/docs/deployment/aspire.mdx
@@ -1,6 +1,6 @@
---
title: Local Orchestration with .NET Aspire
-lastUpdated: 2026-09-30
+lastUpdated: 2026-11-03
description: What spins up when you run the AppHost, and the decisions behind the local topology - Postgres, Valkey, RustFS, the migrator, the API, and both React apps.
sidebar:
label: Local Orchestration (Aspire)
@@ -31,7 +31,7 @@ documents what it starts and **why** it's wired the way it is.
| pgAdmin | *(sidecar)* | Web UI on **:5050**, auto-discovers every database on the server | Persistent |
| Valkey | `redis` | Distributed cache (HybridCache L2) + SignalR backplane + Data Protection key ring (**Valkey** - a Redis-compatible, BSD-licensed Redis fork; resource name stays `redis`) | Persistent (data volume) |
| RedisInsight | `redis-insight` | Key browser on **:5540**, pre-connected to the Valkey instance for inspecting cache keys in dev | Persistent |
-| RustFS | `rustfs` | S3-compatible object storage (`rustfs/rustfs:1.0.0`), **:9000** (S3 API) / **:9001** (web console) | Persistent (data volume) |
+| RustFS | `rustfs` | S3-compatible object storage (`rustfs/rustfs:1.0.4`), **:9000** (S3 API) / **:9001** (web console) | Persistent (data volume) |
| RustFS init | `rustfs-init` | One-shot (`amazon/aws-cli`): creates the `fsh-uploads` bucket + an anonymous `GetObject` policy on `public/*` only (re-applied every run), then exits | Run-once |
| DB migrator | `fsh-starter-db-migrator` | One-shot: applies migrations across the tenant catalog + every tenant's module DBs (`apply --seed`, so the root admin exists), then exits | Run-once |
| Demo seeder | `fsh-starter-demo-seeder` | One-shot, **dev-only**: runs `seed-demo` after the migrator to provision the `acme`/`globex` demo tenants + users, then exits | Run-once |
@@ -93,7 +93,7 @@ two contexts.
### Valkey for caching and realtime, with a RedisInsight browser
The cache engine is **Valkey** (a Redis-compatible, BSD-licensed Redis fork),
-pulled as `valkey/valkey:9.1.0`. It speaks the Redis protocol (RESP), so the
+pulled as `valkey/valkey:9.1.3`. It speaks the Redis protocol (RESP), so the
.NET client (`StackExchange.Redis`) and every `CachingOptions:Redis` config key
are unchanged - and the resource keeps the name `redis` in code. It backs the
HybridCache L2, the SignalR backplane, and the Data Protection key ring
@@ -115,7 +115,8 @@ Keep it that way.
`AddPostgres("postgres")` doesn't pin an image tag - it tracks whatever the
-Aspire hosting package defaults to (PostgreSQL 18 as of Aspire 13.4). When a
+Aspire hosting package defaults to (PostgreSQL 18 since Aspire 13.4; Aspire 13.6
+still ships `postgres:18.3`, so moving from 13.5 to 13.6 keeps your volume). When a
package update jumps a Postgres **major** version, the persistent
`*-postgres-data` volume written by the older major can't be read and the
container exits. Recovery for local dev data: remove the volume and relaunch -
diff --git a/src/content/docs/deployment/aws-terraform.mdx b/src/content/docs/deployment/aws-terraform.mdx
index 81c2cfeb..b86bbe34 100644
--- a/src/content/docs/deployment/aws-terraform.mdx
+++ b/src/content/docs/deployment/aws-terraform.mdx
@@ -1,6 +1,6 @@
---
title: Deploy to AWS with Terraform
-lastUpdated: 2026-09-30
+lastUpdated: 2026-11-03
description: End-to-end AWS deployment - prerequisites, bootstrapping the state backend, configuring an environment, and the one-command deploy that ships the API and both React apps.
sidebar:
label: AWS (Terraform)
@@ -69,7 +69,7 @@ ACM cert (in `us-east-1` for CloudFront) and DNS when you're ready.
The configuration pins `required_version >= 1.15.4` and the AWS provider to
-`~> 6.46`. An older CLI will be rejected at `terraform init`. Upgrade with e.g.
+`~> 6.71`. An older CLI will be rejected at `terraform init`. Upgrade with e.g.
`choco upgrade terraform` (Windows), `brew upgrade terraform` (macOS), or your
package manager. A committed `.terraform.lock.hcl` keeps provider versions
identical across machines and CI.
diff --git a/src/content/docs/deployment/ci-cd.mdx b/src/content/docs/deployment/ci-cd.mdx
index 0a98c73e..125e2d59 100644
--- a/src/content/docs/deployment/ci-cd.mdx
+++ b/src/content/docs/deployment/ci-cd.mdx
@@ -1,6 +1,6 @@
---
title: CI/CD Pipeline
-lastUpdated: 2026-06-11
+lastUpdated: 2026-11-03
description: The GitHub Actions setup - path-scoped backend and frontend pipelines, a pinned SDK, deduplicated test + coverage, CodeQL, the template smoke guard, and how container images and NuGet packages are published.
sidebar:
label: CI/CD
@@ -39,7 +39,7 @@ Triggers on pushes to `main`, `v*` tags, and PRs to `main`, plus manual `workflo
| **Detect changes** | - | always | A `dorny/paths-filter` step decides whether backend code changed. On any push/tag/dispatch it's always `true`; on a PR it's `true` only when `src/**`, `global.json`, `coverage.runsettings`, or the workflow itself changed. Every heavy job gates on this. |
| **Unit Tests** | Detect changes | backend changed | `dotnet build -c Release -warnaserror` (the build gate), an audit that **fails on directly-referenced vulnerable packages** (transitive advisories are reported, not blocking), then the 12 unit/architecture test projects **with coverage collection**. Uploads the coverage as an artifact. |
| **Integration Tests** | Detect changes | backend changed | `Integration.Tests` + `Integration.Middleware.Tests` via `WebApplicationFactory` + Testcontainers (Docker on the runner), **with coverage collection**. Uploads its coverage as an artifact. |
-| **DbMigrator Container Smoke** | Detect changes | backend changed | Publishes the `fsh-db-migrator` image (Dockerfile-less SDK publish) and runs `apply --catalog-only` against an ephemeral Postgres 17 - asserts it finishes successfully. Catches container-publish and DI-graph regressions. |
+| **DbMigrator Container Smoke** | Detect changes | backend changed | Publishes the `fsh-db-migrator` image (Dockerfile-less SDK publish) and runs `apply --catalog-only` against an ephemeral Postgres 18 - asserts it finishes successfully. Catches container-publish and DI-graph regressions. |
| **Coverage Gate** | Unit + Integration | backend changed | **Merges** the coverage artifacts from the two test jobs with ReportGenerator and **fails if line coverage drops below 80%**. It does *not* re-run the tests - a ratchet; raise `MIN_LINE` as coverage grows. |
| **Publish Dev Containers** | Unit + Integration | push to `main` | Publishes the API **and** DbMigrator images to GHCR tagged `dev-` and `dev-latest`. |
| **Publish Release** | Unit + Integration | `v*` tag, or `workflow_dispatch` on `main` | Packs the `fsh` CLI + the `dotnet new` template, pushes both to NuGet.org, and publishes the API + DbMigrator images to GHCR tagged `` and `latest`. |
diff --git a/src/content/docs/getting-started/introduction.mdx b/src/content/docs/getting-started/introduction.mdx
index 25c7a209..4b366016 100644
--- a/src/content/docs/getting-started/introduction.mdx
+++ b/src/content/docs/getting-started/introduction.mdx
@@ -1,6 +1,6 @@
---
title: Introduction
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
description: What fullstackhero is, what it ships with, and how it's put together.
sidebar:
label: Introduction
@@ -145,10 +145,10 @@ The API never mutates data on startup. Demo data (acme/globex tenants, sample ca
| Storage | Local filesystem or S3-compatible (AWS SDK) |
| Messaging | RabbitMQ client (optional) |
| Logging | Serilog 4 (structured) |
-| Tracing / metrics | OpenTelemetry 1.19 (OTLP exporter) |
+| Tracing / metrics | OpenTelemetry 1.20 (OTLP exporter) |
| API docs | OpenAPI 10 + Scalar UI |
| Errors | `ProblemDetails` (RFC 9457) via global exception handler |
-| Orchestration | .NET Aspire 13.5 (Postgres + Valkey + RustFS + API + both React apps) |
+| Orchestration | .NET Aspire 13.6 (Postgres + Valkey + RustFS + API + both React apps) |
### Frontends
diff --git a/src/content/docs/getting-started/prerequisites.mdx b/src/content/docs/getting-started/prerequisites.mdx
index 2aaecb0b..44074081 100644
--- a/src/content/docs/getting-started/prerequisites.mdx
+++ b/src/content/docs/getting-started/prerequisites.mdx
@@ -1,6 +1,6 @@
---
title: Prerequisites
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
description: What you need installed locally before running a fullstackhero project.
sidebar:
order: 2
@@ -15,7 +15,7 @@ A short checklist. The rest of Getting Started assumes these are in place.
## Required
-- **.NET 10 SDK** - from [dotnet.microsoft.com/download](https://dotnet.microsoft.com/download). The repo's `global.json` pins SDK `10.0.100` (rolling forward to the latest feature band).
+- **.NET 10 SDK** - from [dotnet.microsoft.com/download](https://dotnet.microsoft.com/download). The repo's `global.json` pins SDK `10.0.400` as the floor (rolling forward to the latest feature band, `10.0.403` at the time of writing).
- **Docker** - used for the Postgres, Valkey, and RustFS containers (Aspire and the integration tests rely on it). Docker Desktop on macOS / Windows, Docker Engine on Linux.
- **Git** - for cloning the repo.
diff --git a/src/content/docs/testing/fixtures-and-seed-data.mdx b/src/content/docs/testing/fixtures-and-seed-data.mdx
index de5a7d6f..4c0a5e72 100644
--- a/src/content/docs/testing/fixtures-and-seed-data.mdx
+++ b/src/content/docs/testing/fixtures-and-seed-data.mdx
@@ -1,6 +1,6 @@
---
title: Fixtures & seed data
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
description: The shared test factory (containers, host, seeded root tenant + admin) used across the integration suite, and the service swaps that keep tests deterministic.
sidebar:
label: Fixtures & seed data
@@ -22,7 +22,7 @@ The whole suite shares one database across hundreds of tests. Give everything yo
| Piece | Type | Role |
|---|---|---|
-| `FshWebApplicationFactory` | `WebApplicationFactory, IAsyncLifetime` | Owns the `postgres:17-alpine` + `rustfs/rustfs:1.0.0` (generic `ContainerBuilder`) Testcontainers, builds the in-process host, migrates + seeds |
+| `FshWebApplicationFactory` | `WebApplicationFactory, IAsyncLifetime` | Owns the `postgres:18-alpine` + `rustfs/rustfs:1.0.4` (generic `ContainerBuilder`) Testcontainers, builds the in-process host, migrates + seeds |
| `FshCollectionDefinition` | `ICollectionFixture` | Shares **one** factory instance across the whole suite (`[Collection(FshCollectionDefinition.Name)]`) |
| `AuthHelper` | Plain class, wraps the factory | Issues real tokens via `POST /token/issue` and builds authenticated `HttpClient`s |
| `TestConstants` | Static class | Root tenant id, root admin credentials, JWT settings, per-module base paths |
diff --git a/src/content/docs/testing/integration-tests.mdx b/src/content/docs/testing/integration-tests.mdx
index 1645ecc2..263f2400 100644
--- a/src/content/docs/testing/integration-tests.mdx
+++ b/src/content/docs/testing/integration-tests.mdx
@@ -1,6 +1,6 @@
---
title: Integration tests
-lastUpdated: 2026-09-25
+lastUpdated: 2026-11-03
description: WebApplicationFactory in-process API tests against Testcontainers Postgres + RustFS - one container set, all modules, real round trips.
sidebar:
label: Integration tests
@@ -12,7 +12,7 @@ seo:
keywords: 'webapplicationfactory dotnet, testcontainers postgres .net 10, integration test asp.net core, in-process api test, testcontainers rustfs s3'
---
-Integration tests in fullstackhero spin up real infrastructure in containers - PostgreSQL 17 and RustFS (S3-compatible) - and exercise the API end-to-end through `Microsoft.AspNetCore.Mvc.Testing`'s `WebApplicationFactory`. One factory (and therefore one container set) serves the entire `Integration.Tests` project via an xUnit collection fixture, so the startup cost is paid once and the per-test cost is small.
+Integration tests in fullstackhero spin up real infrastructure in containers - PostgreSQL 18 and RustFS (S3-compatible) - and exercise the API end-to-end through `Microsoft.AspNetCore.Mvc.Testing`'s `WebApplicationFactory`. One factory (and therefore one container set) serves the entire `Integration.Tests` project via an xUnit collection fixture, so the startup cost is paid once and the per-test cost is small.
Splitting integration tests into per-module projects would mean per-project container startup. The single project shares one factory + one container set across every test class, runs faster, and you don't have to think about container leakage across boundaries.
@@ -23,11 +23,11 @@ Splitting integration tests into per-module projects would mean per-project cont
| Layer | Library | Purpose |
|---|---|---|
| Host | `Microsoft.AspNetCore.Mvc.Testing` | `WebApplicationFactory` for the in-process API |
-| Containers | `Testcontainers.PostgreSql` + generic `Testcontainers` (`ContainerBuilder`) 4.14 | `postgres:17-alpine` + `rustfs/rustfs:1.0.0` per test run |
+| Containers | `Testcontainers.PostgreSql` + generic `Testcontainers` (`ContainerBuilder`) 4.16 | `postgres:18-alpine` + `rustfs/rustfs:1.0.4` per test run |
| HTTP | `HttpClient` from `factory.CreateClient()` | Real HTTP calls through the full middleware pipeline |
| Assertions | Shouldly, NSubstitute, AutoFixture | Same as unit tests |
-There is **no Redis/Valkey container** in the shared harness - `CachingOptions:Redis` is set to an empty string, so HybridCache runs on its in-memory fallback and Hangfire runs on `Hangfire.InMemory`. The one place a real cache engine matters, `Tests/Caching/HybridCacheRedisTests.cs`, spins up its own `valkey/valkey:9.1.0-alpine` container to guard against in-memory-vs-distributed serialization divergence.
+There is **no Redis/Valkey container** in the shared harness - `CachingOptions:Redis` is set to an empty string, so HybridCache runs on its in-memory fallback and Hangfire runs on `Hangfire.InMemory`. The one place a real cache engine matters, `Tests/Caching/HybridCacheRedisTests.cs`, spins up its own `valkey/valkey:9.1.3-alpine` container to guard against in-memory-vs-distributed serialization divergence.
## The fixture pattern
diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro
index b8b44185..2d767c60 100644
--- a/src/layouts/BaseLayout.astro
+++ b/src/layouts/BaseLayout.astro
@@ -121,7 +121,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? siteConfig.url).t
applicationSubCategory: 'Web Application Framework',
operatingSystem: 'Windows, Linux, macOS',
softwareVersion: '10.0.0',
- softwareRequirements: '.NET 10 SDK, PostgreSQL 17, Redis 7, Node.js 20+',
+ softwareRequirements: '.NET 10 SDK, PostgreSQL 18, Valkey 9 (or Redis), Node.js 20+',
downloadUrl: siteConfig.repo,
codeRepository: siteConfig.repo,
programmingLanguage: ['C#', 'TypeScript'],