agentic-team-templates 0.13.2 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +6 -1
  2. package/package.json +1 -1
  3. package/src/index.js +91 -13
  4. package/src/index.test.js +95 -1
  5. package/templates/cpp-expert/.cursorrules/concurrency.md +211 -0
  6. package/templates/cpp-expert/.cursorrules/error-handling.md +170 -0
  7. package/templates/cpp-expert/.cursorrules/memory-and-ownership.md +220 -0
  8. package/templates/cpp-expert/.cursorrules/modern-cpp.md +211 -0
  9. package/templates/cpp-expert/.cursorrules/overview.md +87 -0
  10. package/templates/cpp-expert/.cursorrules/performance.md +223 -0
  11. package/templates/cpp-expert/.cursorrules/testing.md +230 -0
  12. package/templates/cpp-expert/.cursorrules/tooling.md +312 -0
  13. package/templates/cpp-expert/CLAUDE.md +242 -0
  14. package/templates/csharp-expert/.cursorrules/aspnet-core.md +311 -0
  15. package/templates/csharp-expert/.cursorrules/async-patterns.md +206 -0
  16. package/templates/csharp-expert/.cursorrules/dependency-injection.md +206 -0
  17. package/templates/csharp-expert/.cursorrules/error-handling.md +235 -0
  18. package/templates/csharp-expert/.cursorrules/language-features.md +204 -0
  19. package/templates/csharp-expert/.cursorrules/overview.md +92 -0
  20. package/templates/csharp-expert/.cursorrules/performance.md +251 -0
  21. package/templates/csharp-expert/.cursorrules/testing.md +282 -0
  22. package/templates/csharp-expert/.cursorrules/tooling.md +254 -0
  23. package/templates/csharp-expert/CLAUDE.md +360 -0
  24. package/templates/java-expert/.cursorrules/concurrency.md +209 -0
  25. package/templates/java-expert/.cursorrules/error-handling.md +205 -0
  26. package/templates/java-expert/.cursorrules/modern-java.md +216 -0
  27. package/templates/java-expert/.cursorrules/overview.md +81 -0
  28. package/templates/java-expert/.cursorrules/performance.md +239 -0
  29. package/templates/java-expert/.cursorrules/persistence.md +262 -0
  30. package/templates/java-expert/.cursorrules/spring-boot.md +262 -0
  31. package/templates/java-expert/.cursorrules/testing.md +272 -0
  32. package/templates/java-expert/.cursorrules/tooling.md +301 -0
  33. package/templates/java-expert/CLAUDE.md +325 -0
  34. package/templates/javascript-expert/.cursorrules/overview.md +5 -3
  35. package/templates/javascript-expert/.cursorrules/typescript-deep-dive.md +348 -0
  36. package/templates/javascript-expert/CLAUDE.md +34 -3
  37. package/templates/kotlin-expert/.cursorrules/coroutines.md +237 -0
  38. package/templates/kotlin-expert/.cursorrules/error-handling.md +149 -0
  39. package/templates/kotlin-expert/.cursorrules/frameworks.md +227 -0
  40. package/templates/kotlin-expert/.cursorrules/language-features.md +231 -0
  41. package/templates/kotlin-expert/.cursorrules/overview.md +77 -0
  42. package/templates/kotlin-expert/.cursorrules/performance.md +185 -0
  43. package/templates/kotlin-expert/.cursorrules/testing.md +213 -0
  44. package/templates/kotlin-expert/.cursorrules/tooling.md +258 -0
  45. package/templates/kotlin-expert/CLAUDE.md +276 -0
  46. package/templates/swift-expert/.cursorrules/concurrency.md +230 -0
  47. package/templates/swift-expert/.cursorrules/error-handling.md +213 -0
  48. package/templates/swift-expert/.cursorrules/language-features.md +246 -0
  49. package/templates/swift-expert/.cursorrules/overview.md +88 -0
  50. package/templates/swift-expert/.cursorrules/performance.md +260 -0
  51. package/templates/swift-expert/.cursorrules/swiftui.md +260 -0
  52. package/templates/swift-expert/.cursorrules/testing.md +286 -0
  53. package/templates/swift-expert/.cursorrules/tooling.md +285 -0
  54. package/templates/swift-expert/CLAUDE.md +275 -0
@@ -0,0 +1,92 @@
1
+ # C# Expert Overview
2
+
3
+ Principal-level C# engineering. Deep .NET runtime knowledge, modern language features, and production-grade patterns.
4
+
5
+ ## Scope
6
+
7
+ This guide applies to:
8
+ - Web APIs and services (ASP.NET Core, Minimal APIs)
9
+ - Desktop and cross-platform applications (WPF, MAUI, Avalonia)
10
+ - Cloud-native services (Azure Functions, Worker Services)
11
+ - Libraries and NuGet packages
12
+ - Real-time systems (SignalR, gRPC)
13
+ - Background processing (Hosted Services, message consumers)
14
+
15
+ ## Core Philosophy
16
+
17
+ C# is a language of deliberate design. Every feature exists for a reason — use the right tool for the job.
18
+
19
+ - **Type safety is your first line of defense.** Nullable reference types enabled, warnings as errors.
20
+ - **Composition over inheritance.** Interfaces, extension methods, and dependency injection — not deep class hierarchies.
21
+ - **Async all the way down.** Never block on async code. Never use `.Result` or `.Wait()` in application code.
22
+ - **The framework does the heavy lifting.** ASP.NET Core's middleware pipeline, DI container, and configuration system are battle-tested — use them.
23
+ - **Measure before you optimize.** BenchmarkDotNet and dotnet-counters before rewriting anything.
24
+ - **If you don't know, say so.** Admitting uncertainty is professional. Guessing at runtime behavior you haven't verified is not.
25
+
26
+ ## Key Principles
27
+
28
+ 1. **Nullable Reference Types Are Non-Negotiable** — `<Nullable>enable</Nullable>` in every project
29
+ 2. **Prefer Records for Data** — Immutable by default, value semantics, concise syntax
30
+ 3. **Dependency Injection Is the Architecture** — Constructor injection, interface segregation, composition root
31
+ 4. **Errors Are Explicit** — Result patterns for expected failures, exceptions for exceptional conditions
32
+ 5. **Tests Describe Behavior** — Not implementation details
33
+
34
+ ## Project Structure
35
+
36
+ ```
37
+ Solution/
38
+ ├── src/
39
+ │ ├── MyApp.Api/ # ASP.NET Core host (thin — wiring only)
40
+ │ │ ├── Program.cs # Composition root
41
+ │ │ ├── Endpoints/ # Minimal API endpoint definitions
42
+ │ │ └── Middleware/ # Custom middleware
43
+ │ ├── MyApp.Application/ # Use cases, commands, queries (no framework deps)
44
+ │ │ ├── Commands/
45
+ │ │ ├── Queries/
46
+ │ │ └── Interfaces/
47
+ │ ├── MyApp.Domain/ # Core domain (zero dependencies)
48
+ │ │ ├── Entities/
49
+ │ │ ├── ValueObjects/
50
+ │ │ └── Events/
51
+ │ └── MyApp.Infrastructure/ # External concerns (DB, HTTP, messaging)
52
+ │ ├── Persistence/
53
+ │ ├── Services/
54
+ │ └── Configuration/
55
+ ├── tests/
56
+ │ ├── MyApp.UnitTests/
57
+ │ ├── MyApp.IntegrationTests/
58
+ │ └── MyApp.ArchitectureTests/
59
+ ├── Directory.Build.props # Shared project settings
60
+ ├── .editorconfig # Code style enforcement
61
+ └── MyApp.sln
62
+ ```
63
+
64
+ ## Directory.Build.props
65
+
66
+ ```xml
67
+ <Project>
68
+ <PropertyGroup>
69
+ <TargetFramework>net9.0</TargetFramework>
70
+ <Nullable>enable</Nullable>
71
+ <ImplicitUsings>enable</ImplicitUsings>
72
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
73
+ <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
74
+ <AnalysisLevel>latest-recommended</AnalysisLevel>
75
+ </PropertyGroup>
76
+ </Project>
77
+ ```
78
+
79
+ ## Definition of Done
80
+
81
+ A C# feature is complete when:
82
+
83
+ - [ ] `dotnet build --warnaserror` passes with zero warnings
84
+ - [ ] `dotnet test` passes with no failures
85
+ - [ ] Nullable reference types produce no warnings
86
+ - [ ] No `#pragma warning disable` without inline justification
87
+ - [ ] Async methods don't block (no `.Result`, `.Wait()`, `.GetAwaiter().GetResult()`)
88
+ - [ ] All public APIs have XML documentation comments
89
+ - [ ] Error paths are tested
90
+ - [ ] DI registrations verified (no missing services at runtime)
91
+ - [ ] No `TODO` without an associated issue
92
+ - [ ] Code reviewed and approved
@@ -0,0 +1,251 @@
1
+ # C# Performance
2
+
3
+ Measure first. Optimize second. BenchmarkDotNet is your truth.
4
+
5
+ ## Profile Before Optimizing
6
+
7
+ ```csharp
8
+ // BenchmarkDotNet for micro-benchmarks
9
+ [MemoryDiagnoser]
10
+ [SimpleJob(RuntimeMoniker.Net90)]
11
+ public class StringConcatBenchmark
12
+ {
13
+ private readonly string[] _items = Enumerable.Range(0, 1000)
14
+ .Select(i => i.ToString()).ToArray();
15
+
16
+ [Benchmark(Baseline = true)]
17
+ public string Concatenation()
18
+ {
19
+ var result = "";
20
+ foreach (var item in _items)
21
+ result += item;
22
+ return result;
23
+ }
24
+
25
+ [Benchmark]
26
+ public string StringBuilder()
27
+ {
28
+ var sb = new StringBuilder();
29
+ foreach (var item in _items)
30
+ sb.Append(item);
31
+ return sb.ToString();
32
+ }
33
+
34
+ [Benchmark]
35
+ public string StringJoin()
36
+ => string.Join("", _items);
37
+ }
38
+ ```
39
+
40
+ ### Diagnostic Tools
41
+
42
+ | Tool | Purpose |
43
+ |------|---------|
44
+ | `dotnet-counters` | Live runtime metrics (GC, thread pool, exceptions) |
45
+ | `dotnet-trace` | Performance tracing |
46
+ | `dotnet-dump` | Memory dump analysis |
47
+ | `dotnet-gcdump` | GC heap analysis |
48
+ | BenchmarkDotNet | Micro-benchmarks with statistical rigor |
49
+
50
+ ## Allocation Patterns
51
+
52
+ ### Reduce Allocations
53
+
54
+ ```csharp
55
+ // Bad: allocates on every call
56
+ public string FormatName(string first, string last)
57
+ => $"{first} {last}"; // Allocates interpolated string
58
+
59
+ // Good: use string.Create or Span for hot paths
60
+ public string FormatName(ReadOnlySpan<char> first, ReadOnlySpan<char> last)
61
+ => string.Create(first.Length + 1 + last.Length, (first.ToString(), last.ToString()),
62
+ (span, state) =>
63
+ {
64
+ state.Item1.AsSpan().CopyTo(span);
65
+ span[state.Item1.Length] = ' ';
66
+ state.Item2.AsSpan().CopyTo(span[(state.Item1.Length + 1)..]);
67
+ });
68
+
69
+ // Better for most cases: just use StringBuilder
70
+ // Premature optimization with Span is worse than readable code
71
+ ```
72
+
73
+ ### ArrayPool and MemoryPool
74
+
75
+ ```csharp
76
+ // Rent buffers instead of allocating
77
+ public async Task<byte[]> CompressAsync(Stream input, CancellationToken ct)
78
+ {
79
+ var buffer = ArrayPool<byte>.Shared.Rent(8192);
80
+ try
81
+ {
82
+ using var output = new MemoryStream();
83
+ using var compressor = new GZipStream(output, CompressionLevel.Optimal);
84
+
85
+ int bytesRead;
86
+ while ((bytesRead = await input.ReadAsync(buffer, ct)) > 0)
87
+ {
88
+ await compressor.WriteAsync(buffer.AsMemory(0, bytesRead), ct);
89
+ }
90
+
91
+ await compressor.FlushAsync(ct);
92
+ return output.ToArray();
93
+ }
94
+ finally
95
+ {
96
+ ArrayPool<byte>.Shared.Return(buffer);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Object Pooling
102
+
103
+ ```csharp
104
+ // ObjectPool for expensive-to-create objects
105
+ builder.Services.AddSingleton<ObjectPool<StringBuilder>>(
106
+ new DefaultObjectPoolProvider().CreateStringBuilderPool());
107
+
108
+ public class ReportGenerator(ObjectPool<StringBuilder> pool)
109
+ {
110
+ public string Generate(ReportData data)
111
+ {
112
+ var sb = pool.Get();
113
+ try
114
+ {
115
+ sb.AppendLine($"Report: {data.Title}");
116
+ foreach (var item in data.Items)
117
+ sb.AppendLine($"- {item.Name}: {item.Value}");
118
+ return sb.ToString();
119
+ }
120
+ finally
121
+ {
122
+ pool.Return(sb);
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## struct vs class
129
+
130
+ ```csharp
131
+ // Use struct when:
132
+ // - Logically represents a single value
133
+ // - Instance size < 16 bytes (guideline, not rule)
134
+ // - Immutable
135
+ // - Won't be boxed frequently
136
+ public readonly record struct Coordinate(double Lat, double Lon);
137
+
138
+ // Use class when:
139
+ // - Reference semantics needed
140
+ // - Will be used polymorphically
141
+ // - Large (copying structs is expensive)
142
+ // - Needs to be nullable (struct? has overhead)
143
+ ```
144
+
145
+ ## Collection Performance
146
+
147
+ ```csharp
148
+ // FrozenDictionary/FrozenSet for read-heavy, write-once scenarios (.NET 8+)
149
+ private static readonly FrozenDictionary<string, Handler> _handlers =
150
+ new Dictionary<string, Handler>
151
+ {
152
+ ["GET"] = new GetHandler(),
153
+ ["POST"] = new PostHandler(),
154
+ }.ToFrozenDictionary();
155
+
156
+ // Use CollectionsMarshal for high-performance dictionary access
157
+ ref var value = ref CollectionsMarshal.GetValueRefOrAddDefault(
158
+ dictionary, key, out bool exists);
159
+ if (!exists) value = ComputeExpensiveValue(key);
160
+
161
+ // Correct collection type for the job
162
+ // List<T>: random access, append
163
+ // HashSet<T>: uniqueness, O(1) Contains
164
+ // Dictionary<K,V>: O(1) lookup by key
165
+ // Queue<T>/Stack<T>: FIFO/LIFO
166
+ // LinkedList<T>: frequent insert/remove in middle (rare in practice)
167
+ // SortedSet<T>: ordered unique elements
168
+ ```
169
+
170
+ ## EF Core Performance
171
+
172
+ ```csharp
173
+ // Always use AsNoTracking for read-only queries
174
+ var users = await _db.Users
175
+ .AsNoTracking()
176
+ .Where(u => u.IsActive)
177
+ .Select(u => new UserDto(u.Id, u.Name, u.Email)) // Project to DTO
178
+ .ToListAsync(ct);
179
+
180
+ // Avoid N+1 queries — use Include or projection
181
+ // Bad:
182
+ var orders = await _db.Orders.ToListAsync(ct);
183
+ foreach (var order in orders)
184
+ {
185
+ var items = order.Items; // Lazy load = N+1 queries!
186
+ }
187
+
188
+ // Good:
189
+ var orders = await _db.Orders
190
+ .Include(o => o.Items)
191
+ .ToListAsync(ct);
192
+
193
+ // Even better: project to exactly what you need
194
+ var orderSummaries = await _db.Orders
195
+ .Select(o => new OrderSummary(o.Id, o.Total, o.Items.Count))
196
+ .ToListAsync(ct);
197
+
198
+ // Use compiled queries for hot paths
199
+ private static readonly Func<AppDbContext, int, Task<User?>> GetUserById =
200
+ EF.CompileAsyncQuery((AppDbContext db, int id) =>
201
+ db.Users.FirstOrDefault(u => u.Id == id));
202
+ ```
203
+
204
+ ## Caching
205
+
206
+ ```csharp
207
+ // IMemoryCache for single-instance caching
208
+ public async Task<User?> GetUserAsync(int id, CancellationToken ct)
209
+ {
210
+ return await _cache.GetOrCreateAsync($"user:{id}", async entry =>
211
+ {
212
+ entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
213
+ entry.SetAbsoluteExpiration(TimeSpan.FromHours(1));
214
+ entry.SetSize(1); // For bounded caches
215
+ return await _repository.GetByIdAsync(id, ct);
216
+ });
217
+ }
218
+
219
+ // IDistributedCache for multi-instance (Redis, SQL Server)
220
+ // HybridCache (.NET 9+) for stampede protection
221
+ public async Task<User?> GetUserAsync(int id, CancellationToken ct)
222
+ {
223
+ return await _hybridCache.GetOrCreateAsync(
224
+ $"user:{id}",
225
+ async token => await _repository.GetByIdAsync(id, token),
226
+ cancellationToken: ct);
227
+ }
228
+ ```
229
+
230
+ ## Anti-Patterns
231
+
232
+ ```csharp
233
+ // Never: premature optimization without measurement
234
+ // "I think StringBuilder is faster" — prove it with a benchmark
235
+
236
+ // Never: LINQ in hot loops when a simple for loop suffices
237
+ for (int i = 0; i < items.Length; i++) // Fine for hot paths
238
+ Process(items[i]);
239
+
240
+ // Never: multiple enumeration of IEnumerable
241
+ var count = query.Count(); // Executes query
242
+ var list = query.ToList(); // Executes query AGAIN
243
+ // Materialize once: var list = query.ToList(); var count = list.Count;
244
+
245
+ // Never: blocking async in constructors
246
+ public MyService()
247
+ {
248
+ _data = LoadDataAsync().Result; // Deadlock risk
249
+ }
250
+ // Use async factory method or lazy initialization
251
+ ```
@@ -0,0 +1,282 @@
1
+ # C# Testing
2
+
3
+ Test behavior, not implementation. Every test should answer: "what does this code do?"
4
+
5
+ ## Framework Stack
6
+
7
+ | Tool | Purpose |
8
+ |------|---------|
9
+ | xUnit | Test framework (preferred for .NET) |
10
+ | NSubstitute | Mocking (clean syntax, less ceremony than Moq) |
11
+ | FluentAssertions | Readable assertions |
12
+ | Bogus | Test data generation |
13
+ | Testcontainers | Real databases/services in integration tests |
14
+ | Verify | Snapshot testing |
15
+ | ArchUnitNET | Architecture tests |
16
+
17
+ ## Unit Test Structure
18
+
19
+ ```csharp
20
+ public class OrderServiceTests
21
+ {
22
+ private readonly IOrderRepository _repository = Substitute.For<IOrderRepository>();
23
+ private readonly IInventoryService _inventory = Substitute.For<IInventoryService>();
24
+ private readonly OrderService _sut;
25
+
26
+ public OrderServiceTests()
27
+ {
28
+ _sut = new OrderService(_repository, _inventory);
29
+ }
30
+
31
+ [Fact]
32
+ public async Task CreateOrder_WithValidItems_ReturnsOrder()
33
+ {
34
+ // Arrange
35
+ var request = new CreateOrderRequest("customer-1", [new("sku-1", 2)]);
36
+ _inventory.CheckAvailabilityAsync("sku-1", 2, Arg.Any<CancellationToken>())
37
+ .Returns(true);
38
+
39
+ // Act
40
+ var result = await _sut.CreateAsync(request, CancellationToken.None);
41
+
42
+ // Assert
43
+ result.IsSuccess.Should().BeTrue();
44
+ result.Value!.CustomerId.Should().Be("customer-1");
45
+ result.Value.Items.Should().HaveCount(1);
46
+ }
47
+
48
+ [Fact]
49
+ public async Task CreateOrder_WithInsufficientInventory_ReturnsError()
50
+ {
51
+ // Arrange
52
+ var request = new CreateOrderRequest("customer-1", [new("sku-1", 100)]);
53
+ _inventory.CheckAvailabilityAsync("sku-1", 100, Arg.Any<CancellationToken>())
54
+ .Returns(false);
55
+
56
+ // Act
57
+ var result = await _sut.CreateAsync(request, CancellationToken.None);
58
+
59
+ // Assert
60
+ result.IsSuccess.Should().BeFalse();
61
+ result.Error!.Code.Should().Be("INSUFFICIENT_INVENTORY");
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Test Naming
67
+
68
+ ```csharp
69
+ // Pattern: Method_Scenario_ExpectedBehavior
70
+ [Fact]
71
+ public async Task GetByEmail_WhenUserExists_ReturnsUser() { }
72
+
73
+ [Fact]
74
+ public async Task GetByEmail_WhenUserNotFound_ReturnsNull() { }
75
+
76
+ [Fact]
77
+ public async Task Register_WithDuplicateEmail_ReturnsConflictError() { }
78
+
79
+ // Or descriptive phrases
80
+ [Fact]
81
+ public async Task Newly_created_order_has_pending_status() { }
82
+ ```
83
+
84
+ ## Theory (Parameterized Tests)
85
+
86
+ ```csharp
87
+ [Theory]
88
+ [InlineData("", false)]
89
+ [InlineData("a", false)]
90
+ [InlineData("ab", false)]
91
+ [InlineData("abc", true)]
92
+ [InlineData("valid@email.com", true)]
93
+ public void Validate_Password_MinLength(string input, bool expectedValid)
94
+ {
95
+ var result = PasswordValidator.IsValid(input);
96
+ result.Should().Be(expectedValid);
97
+ }
98
+
99
+ // Complex data with MemberData
100
+ [Theory]
101
+ [MemberData(nameof(InvalidOrderTestCases))]
102
+ public async Task CreateOrder_WithInvalidData_ReturnsValidationError(
103
+ CreateOrderRequest request, string expectedErrorCode)
104
+ {
105
+ var result = await _sut.CreateAsync(request, CancellationToken.None);
106
+
107
+ result.IsSuccess.Should().BeFalse();
108
+ result.Error!.Code.Should().Be(expectedErrorCode);
109
+ }
110
+
111
+ public static IEnumerable<object[]> InvalidOrderTestCases()
112
+ {
113
+ yield return [new CreateOrderRequest("", []), "VALIDATION"];
114
+ yield return [new CreateOrderRequest("c1", []), "VALIDATION"];
115
+ }
116
+ ```
117
+
118
+ ## Integration Tests with WebApplicationFactory
119
+
120
+ ```csharp
121
+ public class OrderEndpointTests : IClassFixture<WebApplicationFactory<Program>>
122
+ {
123
+ private readonly HttpClient _client;
124
+
125
+ public OrderEndpointTests(WebApplicationFactory<Program> factory)
126
+ {
127
+ _client = factory.WithWebHostBuilder(builder =>
128
+ {
129
+ builder.ConfigureServices(services =>
130
+ {
131
+ // Replace real services with test doubles
132
+ services.RemoveAll<IOrderRepository>();
133
+ services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
134
+ });
135
+ }).CreateClient();
136
+ }
137
+
138
+ [Fact]
139
+ public async Task POST_orders_returns_created_for_valid_request()
140
+ {
141
+ // Arrange
142
+ var request = new CreateOrderRequest("customer-1", [new("sku-1", 1)]);
143
+
144
+ // Act
145
+ var response = await _client.PostAsJsonAsync("/orders", request);
146
+
147
+ // Assert
148
+ response.StatusCode.Should().Be(HttpStatusCode.Created);
149
+ var order = await response.Content.ReadFromJsonAsync<OrderResponse>();
150
+ order!.CustomerId.Should().Be("customer-1");
151
+ }
152
+
153
+ [Fact]
154
+ public async Task GET_orders_id_returns_not_found_for_missing_order()
155
+ {
156
+ var response = await _client.GetAsync("/orders/nonexistent");
157
+ response.StatusCode.Should().Be(HttpStatusCode.NotFound);
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Integration Tests with Testcontainers
163
+
164
+ ```csharp
165
+ public class PostgresOrderRepositoryTests : IAsyncLifetime
166
+ {
167
+ private readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
168
+ .WithImage("postgres:16-alpine")
169
+ .Build();
170
+
171
+ private AppDbContext _dbContext = null!;
172
+
173
+ public async Task InitializeAsync()
174
+ {
175
+ await _postgres.StartAsync();
176
+ var options = new DbContextOptionsBuilder<AppDbContext>()
177
+ .UseNpgsql(_postgres.GetConnectionString())
178
+ .Options;
179
+ _dbContext = new AppDbContext(options);
180
+ await _dbContext.Database.MigrateAsync();
181
+ }
182
+
183
+ public async Task DisposeAsync()
184
+ {
185
+ await _dbContext.DisposeAsync();
186
+ await _postgres.DisposeAsync();
187
+ }
188
+
189
+ [Fact]
190
+ public async Task SaveAndRetrieve_RoundTrips_Correctly()
191
+ {
192
+ var repo = new OrderRepository(_dbContext);
193
+ var order = Order.Create("customer-1", [new OrderItem("sku-1", 2, 9.99m)]);
194
+
195
+ await repo.SaveAsync(order, CancellationToken.None);
196
+ var retrieved = await repo.GetByIdAsync(order.Id, CancellationToken.None);
197
+
198
+ retrieved.Should().NotBeNull();
199
+ retrieved!.CustomerId.Should().Be("customer-1");
200
+ retrieved.Items.Should().HaveCount(1);
201
+ }
202
+ }
203
+ ```
204
+
205
+ ## Architecture Tests
206
+
207
+ ```csharp
208
+ public class ArchitectureTests
209
+ {
210
+ private static readonly Architecture Architecture =
211
+ new ArchLoader().LoadAssemblies(
212
+ typeof(Order).Assembly, // Domain
213
+ typeof(OrderService).Assembly, // Application
214
+ typeof(OrderRepository).Assembly // Infrastructure
215
+ ).Build();
216
+
217
+ [Fact]
218
+ public void Domain_should_not_depend_on_infrastructure()
219
+ {
220
+ Types().That().ResideInNamespace("MyApp.Domain")
221
+ .Should().NotDependOnAny("MyApp.Infrastructure")
222
+ .Check(Architecture);
223
+ }
224
+
225
+ [Fact]
226
+ public void Application_should_not_depend_on_aspnetcore()
227
+ {
228
+ Types().That().ResideInNamespace("MyApp.Application")
229
+ .Should().NotDependOnAny("Microsoft.AspNetCore")
230
+ .Check(Architecture);
231
+ }
232
+ }
233
+ ```
234
+
235
+ ## Test Data Builders
236
+
237
+ ```csharp
238
+ // Bogus for realistic fake data
239
+ public static class TestData
240
+ {
241
+ private static readonly Faker<CreateOrderRequest> OrderFaker = new Faker<CreateOrderRequest>()
242
+ .CustomInstantiator(f => new CreateOrderRequest(
243
+ CustomerId: f.Random.Guid().ToString(),
244
+ Items: f.Make(f.Random.Int(1, 5), () =>
245
+ new OrderItemRequest(f.Commerce.Ean13(), f.Random.Int(1, 10)))));
246
+
247
+ public static CreateOrderRequest ValidOrder() => OrderFaker.Generate();
248
+
249
+ // Builder pattern for specific scenarios
250
+ public static CreateOrderRequest OrderWithItems(int count) =>
251
+ OrderFaker.Generate() with
252
+ {
253
+ Items = Enumerable.Range(0, count)
254
+ .Select(i => new OrderItemRequest($"sku-{i}", 1))
255
+ .ToList()
256
+ };
257
+ }
258
+ ```
259
+
260
+ ## Anti-Patterns
261
+
262
+ ```csharp
263
+ // Never: testing implementation details
264
+ _repository.Received(1).SaveAsync(Arg.Any<Order>(), Arg.Any<CancellationToken>());
265
+ // Test the outcome instead: verify the order was actually persisted
266
+
267
+ // Never: shared mutable state between tests
268
+ private static List<Order> _orders = []; // Tests pollute each other
269
+ // Use fresh state in constructor or Setup
270
+
271
+ // Never: Thread.Sleep in tests
272
+ await Task.Delay(5000); // Flaky and slow
273
+ // Use polling with timeout, or Polly retry, or proper async synchronization
274
+
275
+ // Never: testing trivial code
276
+ [Fact]
277
+ public void Name_getter_returns_name() // Waste of time
278
+ {
279
+ var user = new User { Name = "Alice" };
280
+ user.Name.Should().Be("Alice");
281
+ }
282
+ ```