Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
7aebc02
feat: implement testing architecture redesign (Resolves #23)
Mar 7, 2026
b6e4ff0
docs: complete implementation and walkthrough for testing architectur…
Mar 8, 2026
321d99a
docs: add implementation detail for testing architecture redesign
Mar 8, 2026
1202b6e
docs: finalize testing architecture documentation and polish function…
Mar 8, 2026
21ae65f
fix: resolve IdentityDbContext constructor crash and document [ERR] l…
Mar 8, 2026
6e86f31
fix: restore original TenantInfo property pattern and layout in Ident…
Mar 8, 2026
2b2b299
test(architecture,identity): fix pre-existing failing tests inherited…
Mar 8, 2026
1940025
docs: finalize redesign walkthrough with pg fix description
cesarcastrocuba Mar 9, 2026
108e212
fix(auditing): adjust AuditRecord TenantId length to match pg schema
Mar 14, 2026
b1a1363
fix(eventing): graceful degradation for outbox dispatcher during test…
Mar 14, 2026
aafb94f
fix(auditing): suppress OperationCanceledException during background …
Mar 14, 2026
0115079
fix: resolve remaining compiler, sonarqube, and nuget vulnerability w…
Mar 14, 2026
6bdaca7
fix: ignore Shared.Tests project during test execution
Mar 14, 2026
a336a21
fix(testing): resolve login test failures and warnings in isolated br…
Mar 14, 2026
0326c17
chore(cleanup): remove .agents/GEMINI.md and fix remaining logging wa…
Mar 14, 2026
6d1acc7
docs: restore module-specific tests to guide and add new tests
Mar 14, 2026
d4357f7
fix(pr): remove breaking unit tests while keeping doc restoration
Mar 14, 2026
847953a
docs: explicitly formalize NSubstitute over Moq in testing guide
Mar 15, 2026
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
43 changes: 25 additions & 18 deletions .claude/skills/testing-guide/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ FSH uses a layered testing strategy with architecture tests as guardrails.
```
src/Tests/
├── Architecture.Tests/ # Enforces layering rules
├── Generic.Tests/ # Shared test utilities
├── Shared.Tests/ # Core infrastructure
├── Integration.Tests/ # Database & Mediator testing (No HTTP)
├── Functional.Tests/ # End-to-End vertical slices via HTTP
├── Spec.Tests/ # BDD Acceptance specs
├── Generic.Tests/ # Shared test utilities & framework unit tests
├── Identity.Tests/ # Identity module tests
├── Multitenancy.Tests/ # Multitenancy module tests
└── Auditing.Tests/ # Auditing module tests
```

> **Note:** For domain logic unit tests, use **NSubstitute ONLY**. Do not use `Moq` or `InMemoryDatabase`.

## Architecture Tests

Architecture tests enforce module boundaries and layering. They run on every build.
Expand Down Expand Up @@ -71,19 +77,19 @@ public class ArchitectureTests
```csharp
public class Create{Entity}HandlerTests
{
private readonly Mock<IRepository<{Entity}>> _repositoryMock;
private readonly Mock<ICurrentUser> _currentUserMock;
private readonly IRepository<{Entity}> _repositoryMock;
private readonly ICurrentUser _currentUserMock;
private readonly Create{Entity}Handler _handler;

public Create{Entity}HandlerTests()
{
_repositoryMock = new Mock<IRepository<{Entity}>>();
_currentUserMock = new Mock<ICurrentUser>();
_currentUserMock.Setup(x => x.TenantId).Returns("test-tenant");
_repositoryMock = Substitute.For<IRepository<{Entity}>>();
_currentUserMock = Substitute.For<ICurrentUser>();
_currentUserMock.TenantId.Returns("test-tenant");

_handler = new Create{Entity}Handler(
_repositoryMock.Object,
_currentUserMock.Object);
_repositoryMock,
_currentUserMock);
}

[Fact]
Expand All @@ -92,17 +98,17 @@ public class Create{Entity}HandlerTests
// Arrange
var command = new Create{Entity}Command("Test", 99.99m);
_repositoryMock
.Setup(x => x.AddAsync(It.IsAny<{Entity}>(), It.IsAny<CancellationToken>()))
.AddAsync(Arg.Any<{Entity}>(), Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);

// Act
var result = await _handler.Handle(command, CancellationToken.None);

// Assert
result.Id.Should().NotBeEmpty();
_repositoryMock.Verify(x => x.AddAsync(
It.Is<{Entity}>(e => e.Name == "Test" && e.Price == 99.99m),
It.IsAny<CancellationToken>()), Times.Once);
await _repositoryMock.Received(1).AddAsync(
Arg.Is<{Entity}>(e => e.Name == "Test" && e.Price == 99.99m),
Arg.Any<CancellationToken>());
}
}
```
Expand Down Expand Up @@ -215,9 +221,10 @@ dotnet test --filter "FullyQualifiedName~Create{Entity}HandlerTests"

## Key Rules

1. **Architecture tests are mandatory** - They enforce module boundaries
2. **Validators need tests** - Cover edge cases
3. **Handlers need tests** - Mock dependencies
4. **Entities need tests** - Test factory methods and domain logic
5. **Use FluentAssertions** - `.Should()` syntax
6. **Use Moq for mocking** - `Mock<T>` pattern
1. **Architecture tests are mandatory** - They enforce module boundaries.
2. **No InMemoryDatabase** - EF Core InMemory provider is an anti-pattern. Use robust `Testcontainers` (PostgreSQL/Redis) for Integration/Functional tests, and strictly `NSubstitute` for Unit Tests.
3. **Integration Tests** - Inherit from `BaseIntegrationTest`. Test commands/queries through `ISender` without HTTP overhead.
4. **Functional Tests** - Inherit from `BaseFunctionalTest`. Test the full vertical slice via `HttpClient` (routing, auth, middlewares, DB).
5. **Spec-Driven Tests Workflow** - Do NOT create separate branches for testing. Tests must be written *during* the feature implementation branch (Red-Green-Refactor) to satisfy the SDD process.
6. **Use Shouldly** - `.ShouldBe()` syntax.
7. **Use NSubstitute for mocking** - `Substitute.For<T>()` pattern.
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ src/
│ ├── Multitenancy/ # Tenant management (Finbuckle)
│ └── Auditing/ # Audit logging
├── Playground/ # Reference application
└── Tests/ # Architecture + unit tests
└── Tests/ # Architecture, Integration, Functional & Spec Tests
```

## The Pattern
Expand Down
20 changes: 20 additions & 0 deletions docs/specs/testing-architecture-redesign/1-specify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Specification: Testing Architecture Redesign

## 1. Description
The current test suite has yielded false positives, masking API failures regarding Authentication, Middlewares, and Database Migrations. The use of `Microsoft.EntityFrameworkCore.InMemory` bypasses critical relational DB checks and ignores ASP.NET Core pipelines. We need to redesign the testing architecture to logically separate concerns into pure Unit tests, Integration tests, Functional tests, and Acceptance/Spec tests, powered by Docker-based `Testcontainers`.

## 2. Requirements & User Stories
- **Requirement 1**: Prevent false positives by using a real ephemeral database (via Testcontainers) instead of `InMemoryDatabase` for integration and functional tests.
- **Requirement 2**: Centralize the shared test infrastructure (Testcontainers, Respawn, WebApplicationFactory) into a new `Tests.Shared` core project to prevent code duplication.
- **Requirement 3**: Isolate tests that only verify Mediator handlers and DB constraints (no HTTP layer) into a new `Integration.Tests` project.
- **Requirement 4**: Isolate vertical slice tests (HTTP requests down to the DB) into a new `Functional.Tests` project.
- **Requirement 5**: Evolve the existing `Spec.Tests` to inherit from the functional testing infrastructure, allowing true BDD/Acceptance testing against a real HTTP and DB environment.
- **Requirement 6**: Progressively clean up the existing `*.Tests` projects to remove `InMemoryDatabase` in favor of pure mock-based unit tests (`NSubstitute`).

## 3. Acceptance Criteria
- [ ] `Directory.Packages.props` updated with `Testcontainers`, `Respawn`, and `Microsoft.AspNetCore.Mvc.Testing`.
- [ ] `Tests.Shared` project created and handles Testcontainer Orchestration.
- [ ] `Playground.Api/Program.cs` is made visible.
- [ ] `Functional.Tests` project created, configured to use `Tests.Shared`, and containing at least 1 working HTTP Login test.
- [ ] `Integration.Tests` project created, configured to use `Tests.Shared` (direct Mediator testing).
- [ ] `Spec.Tests` refactored/configured to utilize the new infrastructure for acceptance tests.
15 changes: 15 additions & 0 deletions docs/specs/testing-architecture-redesign/2-clarify.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Clarifications: Testing Architecture Redesign

## Unresolved Questions
*(None. All questions were resolved during the previous deep analysis phase).*

## Decisions Made

1. **`InMemoryDatabase` Deprecation:** It was clarified that `InMemoryDatabase` is an anti-pattern for evaluating EF Core configurations since it does not support migrations or relational constraints. **Decision:** We will progressively remove `InMemoryDatabase` usage from existing `*.Tests` in favor of pure mock-based unit tests (`NSubstitute`).
2. **`Testcontainers` Usage:** It was questioned whether there are better alternatives to Testcontainers. **Decision:** Testcontainers (orchestrating ephemeral Docker instances of PostgreSQL and Redis) is the optimal, industry-standard approach to ensure isolated, repeatable integration testing without flaky shared state.
3. **Preventing Code Duplication:** We need to avoid duplicating heavy Testcontainer and WebApplicationFactory infrastructure across Integration, Functional, and Spec tests. **Decision:** We will introduce a new `Tests.Shared` (or `Shared.Tests`) core project that will encapsulate the base database fixtures, authentication helpers, and Docker orchestrators. The other test projects will reference this shared core.
4. **Scope of Functional Testing:** Should we test every single endpoint? **Decision:** No. We will apply the Testing Pyramid. Functional tests will cover the "Critical Path" (e.g., Auth, Tenancy lifecycle, User creation), Integration tests will cover complex Data queries, and Unit tests (Mocks) will cover 100% of business/domain logic.
5. **Initial Test Coverage (Validating the Infrastructure):** How do we prove this new architecture works? **Decision:** We do not need "tests for tests". Instead, we will write **one representative test per new layer** during this implementation phase to prove the wiring is correct:
- *Functional Layer:* A test hitting `/api/v1/tokens` (Login) to prove `WebApplicationFactory`, `HttpClient`, and Docker DB are routing HTTP successfully.
- *Integration Layer:* A test directly injecting a `GetTenantStatusQuery` (or similar DB-heavy query) into Mediator to prove DB connection and EF Core translation work without HTTP.
- *Spec Layer:* We will migrate the existing `SetupSanityCheckTests.cs` to inherit from the new Functional infrastructure.
40 changes: 40 additions & 0 deletions docs/specs/testing-architecture-redesign/3-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Technical Plan: Testing Architecture Redesign

## Architecture & Design
To cleanly segregate testing responsibilities and prevent false positives, we will reorganize the test projects into a star hierarchy centered around a new shared infrastructure project. We will transition from non-relational `InMemoryDatabase` testing to actual relational environment testing using `Testcontainers`.

**Hierarchy:**
- `Tests.Shared` (Base Infrastructure)
- `Integration.Tests` (References `Tests.Shared`)
- `Functional.Tests` (References `Tests.Shared` + `Playground.Api`)
- `Spec.Tests` (References `Functional.Tests`)
- `*.Tests` (Unit Tests)

## Proposed Changes (File Level)

### Directory.Packages.props
- `src/Directory.Packages.props`: Add global `<PackageVersion>` references for `Testcontainers.PostgreSql`, `Testcontainers.Redis`, `Microsoft.AspNetCore.Mvc.Testing`, and `Respawn`.

### Solution Structure
- `src/FSH.Framework.slnx`: Add references for the new `Shared.Tests`, `Integration.Tests`, and `Functional.Tests` projects.

### Core API
- `src/Playground/Playground.Api/Program.cs`: Add `public partial class Program { }` at the end of the file to allow visibility for the `WebApplicationFactory`.

### `Tests.Shared` (New Core Component)
- `src/Tests/Shared.Tests/Shared.Tests.csproj`: New xUnit project.
- `src/Tests/Shared.Tests/Infrastructure/CustomWebApplicationFactory.cs`: Overrides the host builder to spin up PostgreSQL and Redis Testcontainers and injects their connection strings into the test configuration.

### `Functional.Tests` (New Component)
- `src/Tests/Functional.Tests/Functional.Tests.csproj`: New xUnit project referencing `Shared.Tests` and `Playground.Api`.
- `src/Tests/Functional.Tests/Infrastructure/BaseFunctionalTest.cs`: Base class implementing `IClassFixture<CustomWebApplicationFactory>`, exposing an authenticated `HttpClient`.
- `src/Tests/Functional.Tests/Identity/TokenEndpointTests.cs`: A functional test ensuring the `/api/v1/tokens` endpoint works end-to-end with the real database.

### `Integration.Tests` (New Component)
- `src/Tests/Integration.Tests/Integration.Tests.csproj`: New xUnit project referencing `Shared.Tests` and `Core`.
- `src/Tests/Integration.Tests/Infrastructure/BaseIntegrationTest.cs`: Base class exposing `ISender` (Mediator) and `DbContext` for direct command testing (sidestepping HTTP).

## Testing Strategy
- **Integration Specs (`Spec.Tests`)**: Will be configured to inherit from `BaseFunctionalTest` to use the Testcontainers infrastructure for BDD scenarios.
- **Unit Tests (`*.Tests`)**: No immediate changes required to existing tests other than laying the groundwork for future cleanup of `InMemoryDatabase` usage.
- **Functional Tests (`Functional.Tests`)**: We will implement 1 core Critical Path test (Login/Tokens) to prove the new `WebApplicationFactory` architecture functions correctly.
34 changes: 34 additions & 0 deletions docs/specs/testing-architecture-redesign/4-tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Implementation Tasks: Testing Architecture Redesign

## 1. Test Setup (Red Phase)
*Write failing tests first to define the success criteria for the new infrastructure.*
- [x] **Functional:** Write `Identity_Login_ShouldReturnValidToken_WhenCredentialsAreCorrect` in `Functional.Tests`.
- [x] **Integration:** Write `Tenant_ShouldBeRetrieved_WhenExistsInDatabase_ViaMediator` in `Integration.Tests`.
- [x] **Spec:** Refactor `SetupSanityCheckTests.cs` in `Spec.Tests` to inherit from the new shared infrastructure.

## 2. Implementation (Green)
### 2.1 Shared Infrastructure
- [x] Add `Testcontainers`, `Testcontainers.PostgreSql`, `Testcontainers.Redis`, `Respawn`, and `Microsoft.AspNetCore.Mvc.Testing` to `Directory.Packages.props`.
- [x] Add `public partial class Program { }` to `src/Playground/Playground.Api/Program.cs`.
- [x] Create project `src/Tests/Shared.Tests/Shared.Tests.csproj`.
- [x] Implement `CustomWebApplicationFactory.cs` (orchestrating Docker containers) in `Shared.Tests`.
- [x] Add `Shared.Tests` to `FSH.Framework.slnx`.

### 2.2 Functional Layer
- [x] Create project `src/Tests/Functional.Tests/Functional.Tests.csproj` referencing `Shared.Tests` and `Playground.Api`.
- [x] Implement `BaseFunctionalTest.cs` (handling `HttpClient` and JWT Token generation) in `Functional.Tests`.
- [x] Execute `Identity_Login_ShouldReturnValidToken_WhenCredentialsAreCorrect` and ensure it passes (Green).
- [x] Add `Functional.Tests` to `FSH.Framework.slnx`.

### 2.3 Integration Layer
- [x] Create project `src/Tests/Integration.Tests/Integration.Tests.csproj` referencing `Shared.Tests`.
- [x] Implement `BaseIntegrationTest.cs` (exposing `ISender` without HTTP) in `Integration.Tests`.
- [x] Add `Integration.Tests` to `FSH.Framework.slnx`.

### 2.4 Spec Layer Alignment
- [x] Add `<ProjectReference>` to `Functional.Tests` inside `src/Tests/Spec.Tests/Spec.Tests.csproj`.

## 3. Verification & Polish
- [x] Run `dotnet build src/FSH.Framework.slnx` and ensure there are 0 warnings.
- [x] Ensure Docker is running and run `dotnet test src/Tests/Functional.Tests`.
- [x] (Optional Cleanup) Ensure the solution builds cleanly and tests discover correctly in Visual Studio / Test Explorer.
21 changes: 21 additions & 0 deletions docs/specs/testing-architecture-redesign/5-implement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Phase 5: Implementation - Testing Architecture Redesign

The implementation phase followed a modular approach, centralizing testing infrastructure while ensuring loose coupling between layers.

## 1. Shared Infrastructure (`Shared.Tests`)
- **Container Orchestration**: Implemented `CustomWebApplicationFactory` using `Testcontainers.PostgreSql` and `Testcontainers.Redis`.
- **Job Synchronization**: Overrode `IJobService` with `InlineJobService` to ensure background provisioning tasks execute synchronously during tests.
- **Contract Handling**: Configured JSON serialization to match the API's naming conventions (e.g., `AccessToken` vs `Token`).

## 2. Integrated Fixes (Tier 0 Tenancy)
To support functional tests, critical multi-tenancy fixes from the `pr/tenancy-isolation-nomigration` branch were integrated:
- **Identity Multi-tenancy**: Re-applied `.IsMultiTenant()` to `Group`, `GroupRole`, and `UserGroup` configurations.
- **Persistence Safety**: Overrode `SaveChangesAsync` in `IdentityDbContext` with `TenantNotSetMode = Overwrite`.

## 3. Architecture Guard Updates
Refined the architecture rules to align with its modular monolith design while maintaining strict boundaries:
- **Dependency Rules**: Modified `BuildingBlocksIndependenceTests` to allow `*.Contracts` and `Identity.Contracts` dependencies, as these are intended for cross-module communication via interfaces.

## 4. Stability Improvements
- **Endpoint Discovery**: Corrected the Identity token issuance URL to `/api/v1/identity/token/issue` in test requests.
- **CI/CD Alignment**: Resolved all warnings related to compiler strictness (CA1515, CA1822) within the Test projects.
Loading