Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
530100f
Enhance authentication and security tests for multi-tenant scenarios
aalmada Feb 16, 2026
bad734e
refactor: Update comment formatting in ResendVerification_RespectsCoo…
aalmada Feb 16, 2026
21a0ddf
refactor: Enhance AddPasskeyToUserAsync to update security stamp for …
aalmada Feb 16, 2026
cd7ab75
chore: Add VSCode configuration files for extensions and launch settings
aalmada Feb 16, 2026
fa4c277
refactor: Update DatabaseHelpers and test files to ensure proper disp…
aalmada Feb 16, 2026
c6cdcbb
refactor: Simplify User_RegisteredInSourceTenant_CanLoginInSourceTena…
aalmada Feb 16, 2026
ab116a1
refactor: Enhance rate limiting configuration to support disabling fo…
aalmada Feb 16, 2026
be7bd8c
refactor: Update multi-tenancy handling and improve error responses i…
aalmada Feb 16, 2026
666745b
refactor: Update favorite book tests to wait for projection completion
aalmada Feb 16, 2026
f1e31b8
refactor: Enhance JWT security stamp validation and update related tests
aalmada Feb 17, 2026
992133e
refactor: Implement security stamp validation for refresh tokens and …
aalmada Feb 18, 2026
2380645
Refactor tests to improve tenant isolation and security checks
aalmada Feb 18, 2026
8308d5d
refactor: Update security stamp handling in password management and a…
aalmada Feb 18, 2026
368fa98
Refactor JWT authentication events and enhance error handling
aalmada Feb 20, 2026
62ab4ea
refactor: Add missing package references for SkiaSharp and WolverineFx
aalmada Feb 20, 2026
04f0ee2
fix(test): forge JWT without tenant_id claim in Request_WithNoTenantI…
aalmada Feb 20, 2026
2bc09c9
fix(test): verify original account survives re-registration in Regist…
aalmada Feb 20, 2026
592912a
fix(test): assert both pruned tokens are invalid in RefreshToken_Keep…
aalmada Feb 20, 2026
d5796ec
fix(test): use real WebAuthn endpoint in Token_AfterAddingPasskey_Bec…
aalmada Feb 20, 2026
c4739e6
fix(test): create valid book with dependencies in Admin_CanCreateBook…
aalmada Feb 20, 2026
32e8ef4
fix(test): use WebAuthn to test passkey-only user endpoints (fixes 2 …
aalmada Feb 20, 2026
31ecc63
fix(test): resolve two runtime test failures
aalmada Feb 20, 2026
2b2c271
fix(test): update ETag handling in various tests and improve version …
aalmada Feb 20, 2026
c5fbfab
Refactor tests to use version 7 GUIDs for consistency
aalmada Feb 20, 2026
67a8acc
fix(tests): refactor test code to improve readability and maintainabi…
aalmada Feb 20, 2026
93fb63f
fix(tests): update comment for clarity on Admin API GetAuthor behavior
aalmada Feb 20, 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
11 changes: 10 additions & 1 deletion .claude/skills/ops__doctor_check/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ Perform a health check on the development environment to ensure all prerequisite
- **Requirement**: Aspire CLI must be installed.
- *Action*: If missing, see [Install instructions](https://aspire.dev/get-started/install-cli/)

5. **Report Summary**
5. **Playwright Browsers** (for `BookStore.AppHost.Tests`)
- Check if the chromium binary exists inside the build output: `ls tests/BookStore.AppHost.Tests/bin/Debug/net10.0/.playwright/package/` (requires the project to have been built)
- **Requirement**: Chromium must be installed for browser-based integration tests.
- *Action*: If missing or the project hasn't been built yet, run:
```bash
dotnet build tests/BookStore.AppHost.Tests/BookStore.AppHost.Tests.csproj
node tests/BookStore.AppHost.Tests/bin/Debug/net10.0/.playwright/package/index.js install chromium
```

6. **Report Summary**
- If all checks pass: "✅ Environment is healthy"
- If issues found: List specific missing tools with installation instructions

Expand Down
9 changes: 9 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"recommendations": [
"microsoft-aspire.aspire-vscode",
"ms-dotnettools.csdevkit",
"ms-azuretools.vscode-containers",
"editorconfig.editorconfig",
"bits.csharp-test-filter"
]
}
31 changes: 31 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug TUnit Test",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${input:dllPath}",
"args": [
"--treenode-filter",
"${input:testFilter}"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"stopAtEntry": false
}
],
"inputs": [
{
"id": "dllPath",
"type": "command",
"command": "csharp-test-filter.getDllPath"
},
{
"id": "testFilter",
"type": "command",
"command": "csharp-test-filter.getFilter"
}
]
}
32 changes: 29 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,27 @@ Use this file for agent-only context: build and test commands, conventions, and

## Quick Reference

- **Stack**: .NET 10, C# 14, Marten, Wolverine, HybridCache, Aspire
- **Stack**: .NET 10, C# 14, Marten, Wolverine, HybridCache, Aspire, Playwright
- **Solution**: `BookStore.slnx` (new .NET 10 solution format)
- **Common commands**: `dotnet restore`, `aspire run`, `dotnet test`, `dotnet format`
- **Docs**: `docs/getting-started.md`, `docs/guides/`
- **Testing instructions**: `tests/AGENTS.md`

### Running Tests (TUnit)

TUnit-specific arguments must be passed after `--` so they are forwarded as program arguments rather than parsed by `dotnet test`:

```bash
# Run all tests (uses all available cores by default)
dotnet test

# Limit parallelism in resource-constrained environments
dotnet test -- --maximum-parallel-tests 4

# Filter tests by category
dotnet test -- --treenode-filter "/*/*/*/*[Category=Integration]"
```

## Repository Map

- `src/BookStore.ApiService/`: Event-sourced API (Marten + Wolverine)
Expand All @@ -38,9 +53,9 @@ Use this file for agent-only context: build and test commands, conventions, and
2. Write verification first
3. Implement
4. Verify all steps pass
5. Run `dotnet format` to ensure code style compliance
5. Run `dotnet format` to ensure code style compliance. Issues not automatically fixed must be resolved manually.

**A feature is not complete until `dotnet format` has been executed successfully.**
**A feature is not complete until `dotnet format --verify-no-changes` exits with code 0.**

## Code Rules (MUST follow)

Expand All @@ -52,6 +67,7 @@ Use this file for agent-only context: build and test commands, conventions, and
✅ [Test] async Task (TUnit) ❌ [Fact] (xUnit)
✅ WaitForConditionAsync ❌ Task.Delay / Thread.Sleep
✅ [LoggerMessage(...)] ❌ _logger.LogInformation(...) / LogWarning / LogError / etc.
✅ MultiTenancyConstants.* ❌ Hardcoded "*DEFAULT*" / "default"
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AGENTS.md update suggests using MultiTenancyConstants.* instead of hardcoded values (line 55), but the actual constant name is MultiTenancyConstants.DefaultTenantId, not MultiTenancyConstants.*.

The wildcard notation ".*" is typically used in documentation to mean "any member of", but for a code standard that will be copy-pasted or referenced, it would be clearer to show the actual constant name:

✅ MultiTenancyConstants.DefaultTenantId ❌ Hardcoded "*DEFAULT*" / "default"

This makes it immediately clear what constant to use rather than requiring developers to look up what members exist.

Copilot uses AI. Check for mistakes.
```

### Logging Pattern
Expand Down Expand Up @@ -82,6 +98,16 @@ Use this file for agent-only context: build and test commands, conventions, and
- SSE not working: run `/frontend__debug_sse`
- Cache issues: run `/cache__debug_cache`
- Environment issues: run `/ops__doctor_check`
- Playwright browser missing (integration tests fail with browser launch error): install browsers with `node tests/BookStore.AppHost.Tests/bin/Debug/net10.0/.playwright/package/index.js install chromium` (build the project first)

## MCP Servers for Documentation

Use MCP servers to get up-to-date documentation instead of relying on training data:

- **Context7** (`mcp_context7_resolve-library-id` → `mcp_context7_get-library-docs`): Use for any library in the stack — Marten, Wolverine, Aspire, Refit, Bogus, TUnit, etc.
- **Microsoft Learn** (`mcp_microsoftdocs_microsoft_docs_search` / `mcp_microsoftdocs_microsoft_docs_fetch`): Use for .NET, ASP.NET Core, Entity Framework, Azure, and any Microsoft technology.

Always prefer MCP server lookups over guessing API shapes or behaviour from training data.

## Documentation Index

Expand Down
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.15.0" />
<PackageVersion Include="Microsoft.JSInterop" Version="10.0.2" />
<PackageVersion Include="MimeKit" Version="4.14.0" />
<PackageVersion Include="Microsoft.Playwright" Version="1.58.0" />
<PackageVersion Include="MudBlazor" Version="8.15.0" />
<PackageVersion Include="Npgsql" Version="10.0.1" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
Expand All @@ -59,4 +60,4 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.102" />
<PackageVersion Include="Roslynator.Analyzers" Version="4.15.0" />
</ItemGroup>
</Project>
</Project>
20 changes: 16 additions & 4 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ curl -H "Accept-Language: pt-PT" http://localhost:5000/api/categories
-- Connect to the database via PgAdmin

-- View all events
SELECT
SELECT
id,
seq_id,
type,
Expand All @@ -283,12 +283,12 @@ ORDER BY timestamp DESC
LIMIT 10;

-- View events for a specific correlation ID
SELECT * FROM mt_events
SELECT * FROM mt_events
WHERE correlation_id = 'getting-started-001'
ORDER BY timestamp;

-- View a specific stream
SELECT * FROM mt_streams
SELECT * FROM mt_streams
WHERE id = '<book-id>';
```

Expand Down Expand Up @@ -364,6 +364,18 @@ BookStore/

The project uses **TUnit**, a modern testing framework for .NET with built-in code coverage and source-generated tests.

### Playwright Browser Setup

The integration tests (`BookStore.AppHost.Tests`) use **Microsoft.Playwright** for browser-based flows. Playwright browsers must be installed once after building the project:

```bash
dotnet build tests/BookStore.AppHost.Tests/BookStore.AppHost.Tests.csproj
node tests/BookStore.AppHost.Tests/bin/Debug/net10.0/.playwright/package/index.js install chromium
```

> [!NOTE]
> Re-run the install command after a `dotnet clean` or if you switch between `Debug` and `Release` configurations — the `.playwright` directory is regenerated by the build.

### Running Tests

```bash
Expand Down Expand Up @@ -451,7 +463,7 @@ curl http://localhost:5000/health

**Error**: "Container runtime 'docker' was found but appears to be unhealthy"

**Solution**:
**Solution**:
1. Open Docker Desktop
2. Wait for it to fully start
3. Run `aspire run` again
Expand Down
3 changes: 3 additions & 0 deletions src/BookStore.ApiService/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@

**Error Handling**: Use `/lang__problem_details` skill for all errors. Return `Result.Failure(Error.<Type>(code, message)).ToProblemDetails()`.

**CRITICAL**: ALL failures MUST return RFC 7807 ProblemDetails with a machine-readable error code. This includes endpoints, handlers, AND middleware. Never return plain JSON errors.

## Common Mistakes
- ❌ Business logic in endpoints → Put logic in aggregates/handlers
- ❌ Missing SSE notification → Add to `MartenCommitListener`
- ❌ Missing cache invalidation → Call `RemoveByTagAsync` after mutations
- ❌ Manually running Marten async daemon → Async projections are updated by Wolverine
- ❌ Skipping tenant context → Use tenant-scoped sessions and cache keys
- ❌ Ignoring ETag checks → Use `IHaveETag` and `ETagHelper`
- ❌ Returning plain JSON errors → ALL failures must return ProblemDetails with error codes (endpoints, handlers, middleware)

## Project Layout
| Path | Purpose |
Expand Down
103 changes: 52 additions & 51 deletions src/BookStore.ApiService/BookStore.ApiService.csproj
Original file line number Diff line number Diff line change
@@ -1,51 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<!-- Garbage Collection Optimization for Server Workloads -->
<!-- Server GC: Uses multiple heaps and threads for better throughput -->
<ServerGarbageCollection>true</ServerGarbageCollection>

<!-- Concurrent GC: Reduces pause times by running GC concurrently with application -->
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>

<!-- Retain VM: Keeps virtual memory allocated for better performance -->
<RetainVMGarbageCollection>true</RetainVMGarbageCollection>

<!-- Dynamic PGO: Profile-guided optimization for hot paths -->
<TieredCompilation>true</TieredCompilation>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
<NoWarn>$(NoWarn);EXTEXP0018</NoWarn>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BookStore.ServiceDefaults\BookStore.ServiceDefaults.csproj" />
<ProjectReference Include="..\BookStore.ApiService.Analyzers\BookStore.ApiService.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\BookStore.Shared\BookStore.Shared.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Asp.Versioning.Http" />
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" />
<PackageReference Include="Aspire.Azure.Storage.Blobs" />
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" />
<PackageReference Include="Bogus" />
<PackageReference Include="JasperFx.Core" />
<PackageReference Include="Marten" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" />
<PackageReference Include="WolverineFx.Http" />
<PackageReference Include="WolverineFx.Marten" />
<PackageReference Include="MailKit" />
<PackageReference Include="MimeKit" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<!-- Garbage Collection Optimization for Server Workloads -->
<!-- Server GC: Uses multiple heaps and threads for better throughput -->
<ServerGarbageCollection>true</ServerGarbageCollection>

<!-- Concurrent GC: Reduces pause times by running GC concurrently with application -->
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>

<!-- Retain VM: Keeps virtual memory allocated for better performance -->
<RetainVMGarbageCollection>true</RetainVMGarbageCollection>

<!-- Dynamic PGO: Profile-guided optimization for hot paths -->
<TieredCompilation>true</TieredCompilation>
<TieredCompilationQuickJit>true</TieredCompilationQuickJit>
<NoWarn>$(NoWarn);EXTEXP0018</NoWarn>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\BookStore.ServiceDefaults\BookStore.ServiceDefaults.csproj" />
<ProjectReference Include="..\BookStore.ApiService.Analyzers\BookStore.ApiService.Analyzers.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\BookStore.Shared\BookStore.Shared.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Npgsql" />
<PackageReference Include="Asp.Versioning.Http" />
<PackageReference Include="AspNetCore.HealthChecks.NpgSql" />
<PackageReference Include="Aspire.Azure.Storage.Blobs" />
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" />
<PackageReference Include="Bogus" />
<PackageReference Include="JasperFx.Core" />
<PackageReference Include="Marten" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" />
<PackageReference Include="Scalar.AspNetCore" />
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" />
<PackageReference Include="WolverineFx.Http" />
<PackageReference Include="WolverineFx.Marten" />
<PackageReference Include="MailKit" />
<PackageReference Include="MimeKit" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
</ItemGroup>

</Project>
Loading
Loading