@ozerohax/assistagents 0.1.1
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.
- package/README.md +90 -0
- package/dist/cli.js +442 -0
- package/dist/cli.js.map +1 -0
- package/package.json +39 -0
- package/templates/agents/ask/ask.md +126 -0
- package/templates/agents/assist/research/code-research.md +116 -0
- package/templates/agents/assist/research/web-research.md +105 -0
- package/templates/agents/build/dev.md +131 -0
- package/templates/agents/planning/plan.md +103 -0
- package/templates/agents/planning/project.md +122 -0
- package/templates/agents/review/reviewer.md +121 -0
- package/templates/agents/test/tester.md +170 -0
- package/templates/skills/coder/csharp/coder-csharp-aspnetcore-api/SKILL.md +352 -0
- package/templates/skills/coder/csharp/coder-csharp-async-concurrency/SKILL.md +63 -0
- package/templates/skills/coder/csharp/coder-csharp-conventions/SKILL.md +405 -0
- package/templates/skills/coder/csharp/coder-csharp-efcore-config/SKILL.md +384 -0
- package/templates/skills/coder/csharp/coder-csharp-efcore-queries/SKILL.md +434 -0
- package/templates/skills/coder/csharp/coder-csharp-error-handling/SKILL.md +354 -0
- package/templates/skills/coder/csharp/coder-csharp-logging/SKILL.md +362 -0
- package/templates/skills/coder/csharp/coder-csharp-performance/SKILL.md +71 -0
- package/templates/skills/coder/csharp/coder-csharp-security/SKILL.md +57 -0
- package/templates/skills/coder/csharp/coder-csharp-testing/SKILL.md +398 -0
- package/templates/skills/coder/rust/coder-rust-async-concurrency/SKILL.md +63 -0
- package/templates/skills/coder/rust/coder-rust-axum-api/SKILL.md +104 -0
- package/templates/skills/coder/rust/coder-rust-conventions/SKILL.md +57 -0
- package/templates/skills/coder/rust/coder-rust-error-handling/SKILL.md +56 -0
- package/templates/skills/coder/rust/coder-rust-logging/SKILL.md +69 -0
- package/templates/skills/coder/rust/coder-rust-performance/SKILL.md +51 -0
- package/templates/skills/coder/rust/coder-rust-security/SKILL.md +49 -0
- package/templates/skills/coder/rust/coder-rust-serde/SKILL.md +61 -0
- package/templates/skills/coder/rust/coder-rust-sqlx-config/SKILL.md +54 -0
- package/templates/skills/coder/rust/coder-rust-sqlx-queries/SKILL.md +66 -0
- package/templates/skills/coder/rust/coder-rust-testing/SKILL.md +52 -0
- package/templates/skills/coder/rust/coder-rust-tokio/SKILL.md +69 -0
- package/templates/skills/coder/rust/coder-rust-tower-http/SKILL.md +56 -0
- package/templates/skills/coder/typescript/coder-typescript-async-concurrency/SKILL.md +73 -0
- package/templates/skills/coder/typescript/coder-typescript-conventions/SKILL.md +156 -0
- package/templates/skills/coder/typescript/coder-typescript-error-handling/SKILL.md +126 -0
- package/templates/skills/coder/typescript/coder-typescript-logging/SKILL.md +107 -0
- package/templates/skills/coder/typescript/coder-typescript-performance/SKILL.md +83 -0
- package/templates/skills/coder/typescript/coder-typescript-security/SKILL.md +78 -0
- package/templates/skills/coder/typescript/coder-typescript-testing/SKILL.md +111 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-composables/SKILL.md +57 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-core/SKILL.md +65 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-performance/SKILL.md +48 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-primevue/SKILL.md +140 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-primevue/scripts/primevue-docs.js +502 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-reactivity/SKILL.md +61 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-testing/SKILL.md +63 -0
- package/templates/skills/planning/code/planning-code-feature/SKILL.md +148 -0
- package/templates/skills/planning/code/planning-code-fix/SKILL.md +207 -0
- package/templates/skills/planning/project/fast/planning-project-fast-delivery-deploy/SKILL.md +33 -0
- package/templates/skills/planning/project/fast/planning-project-fast-intake-questions/SKILL.md +82 -0
- package/templates/skills/planning/project/fast/planning-project-fast-mvp-slicing/SKILL.md +36 -0
- package/templates/skills/planning/project/fast/planning-project-fast-output-template/SKILL.md +79 -0
- package/templates/skills/planning/project/fast/planning-project-fast-session-plan/SKILL.md +30 -0
- package/templates/skills/planning/project/shared/planning-project-acceptance-criteria/SKILL.md +46 -0
- package/templates/skills/planning/project/shared/planning-project-artifact-storage/SKILL.md +68 -0
- package/templates/skills/planning/project/shared/planning-project-checkpoints-approval/SKILL.md +24 -0
- package/templates/skills/planning/project/shared/planning-project-save-revision/SKILL.md +33 -0
- package/templates/skills/planning/project/shared/planning-project-scope-control/SKILL.md +30 -0
- package/templates/skills/planning/project/shared/planning-project-success-metric-scope/SKILL.md +41 -0
- package/templates/skills/planning/project/shared/planning-project-task-decomposition/SKILL.md +47 -0
- package/templates/skills/planning/project/shared/planning-project-testing-validation/SKILL.md +31 -0
- package/templates/skills/planning/project/standard/planning-project-standard-architecture-adr-lite/SKILL.md +68 -0
- package/templates/skills/planning/project/standard/planning-project-standard-epics-stories/SKILL.md +41 -0
- package/templates/skills/planning/project/standard/planning-project-standard-implementation-readiness-lite/SKILL.md +32 -0
- package/templates/skills/planning/project/standard/planning-project-standard-prd-fr-nfr/SKILL.md +60 -0
- package/templates/skills/planning/project/standard/planning-project-standard-product-brief/SKILL.md +46 -0
- package/templates/skills/planning/project/standard/planning-project-standard-story-to-tasks/SKILL.md +52 -0
- package/templates/skills/research-strategy/research-strategy-code/SKILL.md +80 -0
- package/templates/skills/research-strategy/research-strategy-web/SKILL.md +63 -0
- package/templates/skills/review/review-checklists/SKILL.md +57 -0
- package/templates/skills/review/review-requirements/SKILL.md +42 -0
- package/templates/skills/review/review-strategy/SKILL.md +120 -0
- package/templates/skills/testing/testing-api-manual/SKILL.md +535 -0
- package/templates/skills/testing/testing-automation-web/SKILL.md +81 -0
- package/templates/skills/testing/testing-browser-manual/SKILL.md +528 -0
- package/templates/skills/testing/testing-checklists/SKILL.md +434 -0
- package/templates/skills/testing/testing-strategy/SKILL.md +471 -0
- package/templates/skills/testing/testing-test-cases/SKILL.md +362 -0
- package/templates/skills/testing/testing-triage-bugs/SKILL.md +457 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coder-csharp-testing
|
|
3
|
+
description: C# unit and integration testing best practices. Use when writing tests, setting up test projects, mocking dependencies, or testing with EF Core and ASP.NET Core.
|
|
4
|
+
---
|
|
5
|
+
<skill_overview>
|
|
6
|
+
<purpose>Write effective, maintainable tests following industry best practices</purpose>
|
|
7
|
+
<triggers>
|
|
8
|
+
<trigger>Writing unit tests</trigger>
|
|
9
|
+
<trigger>Setting up integration tests</trigger>
|
|
10
|
+
<trigger>Mocking dependencies</trigger>
|
|
11
|
+
<trigger>Testing EF Core repositories</trigger>
|
|
12
|
+
<trigger>Testing ASP.NET Core APIs</trigger>
|
|
13
|
+
</triggers>
|
|
14
|
+
<sources>
|
|
15
|
+
<source url="https://learn.microsoft.com/en-us/dotnet/core/testing/">Microsoft Testing Documentation</source>
|
|
16
|
+
<source url="https://xunit.net/">xUnit Official Documentation</source>
|
|
17
|
+
</sources>
|
|
18
|
+
</skill_overview>
|
|
19
|
+
<framework_choice>
|
|
20
|
+
<recommendation>Use xUnit for new projects</recommendation>
|
|
21
|
+
<why>
|
|
22
|
+
<reason>Modern design by original NUnit authors</reason>
|
|
23
|
+
<reason>New instance per test (better isolation)</reason>
|
|
24
|
+
<reason>No class-level attributes needed</reason>
|
|
25
|
+
<reason>Community standard for .NET</reason>
|
|
26
|
+
</why>
|
|
27
|
+
<comparison>
|
|
28
|
+
<framework name="xUnit">
|
|
29
|
+
<test_attr>[Fact], [Theory]</test_attr>
|
|
30
|
+
<setup>Constructor + IDisposable</setup>
|
|
31
|
+
<isolation>New instance per test</isolation>
|
|
32
|
+
</framework>
|
|
33
|
+
<framework name="NUnit">
|
|
34
|
+
<test_attr>[Test], [TestCase]</test_attr>
|
|
35
|
+
<setup>[SetUp], [TearDown]</setup>
|
|
36
|
+
<isolation>Same instance per class</isolation>
|
|
37
|
+
</framework>
|
|
38
|
+
<framework name="MSTest">
|
|
39
|
+
<test_attr>[TestMethod]</test_attr>
|
|
40
|
+
<setup>[TestInitialize]</setup>
|
|
41
|
+
<isolation>Same instance per class</isolation>
|
|
42
|
+
</framework>
|
|
43
|
+
</comparison>
|
|
44
|
+
</framework_choice>
|
|
45
|
+
<unit_testing>
|
|
46
|
+
<pattern name="arrange_act_assert" required="true">
|
|
47
|
+
<description>Structure every test with AAA pattern</description>
|
|
48
|
+
<example>
|
|
49
|
+
<code>
|
|
50
|
+
[Fact]
|
|
51
|
+
public void Add_TwoPositiveNumbers_ReturnsSum()
|
|
52
|
+
{
|
|
53
|
+
// Arrange
|
|
54
|
+
var calculator = new Calculator();
|
|
55
|
+
|
|
56
|
+
// Act
|
|
57
|
+
var result = calculator.Add(2, 3);
|
|
58
|
+
|
|
59
|
+
// Assert
|
|
60
|
+
Assert.Equal(5, result);
|
|
61
|
+
}
|
|
62
|
+
</code>
|
|
63
|
+
</example>
|
|
64
|
+
</pattern>
|
|
65
|
+
<naming_convention>
|
|
66
|
+
<format>[Method]_[Scenario]_[ExpectedResult]</format>
|
|
67
|
+
<examples>
|
|
68
|
+
<good>Add_TwoPositiveNumbers_ReturnsSum</good>
|
|
69
|
+
<good>GetUser_NonExistentId_ReturnsNull</good>
|
|
70
|
+
<good>CreateOrder_EmptyCart_ThrowsException</good>
|
|
71
|
+
<bad>Test1, TestAdd, CalculatorTest</bad>
|
|
72
|
+
</examples>
|
|
73
|
+
</naming_convention>
|
|
74
|
+
<assertions>
|
|
75
|
+
<principle>One logical assertion per test</principle>
|
|
76
|
+
<note>Multiple related assertions for same concept are OK</note>
|
|
77
|
+
<example>
|
|
78
|
+
<code>
|
|
79
|
+
[Fact]
|
|
80
|
+
public void CreateUser_ValidInput_ReturnsUserWithCorrectProperties()
|
|
81
|
+
{
|
|
82
|
+
var user = _service.CreateUser("john@example.com", "John");
|
|
83
|
+
|
|
84
|
+
// Multiple assertions for one logical concept (user created correctly)
|
|
85
|
+
Assert.NotNull(user);
|
|
86
|
+
Assert.Equal("john@example.com", user.Email);
|
|
87
|
+
Assert.Equal("John", user.Name);
|
|
88
|
+
Assert.True(user.Id > 0);
|
|
89
|
+
}
|
|
90
|
+
</code>
|
|
91
|
+
</example>
|
|
92
|
+
</assertions>
|
|
93
|
+
<test_isolation>
|
|
94
|
+
<rules>
|
|
95
|
+
<rule>No shared mutable state between tests</rule>
|
|
96
|
+
<rule>Don't depend on test execution order</rule>
|
|
97
|
+
<rule>Each test must be independently runnable</rule>
|
|
98
|
+
<rule>Clean up resources in Dispose</rule>
|
|
99
|
+
</rules>
|
|
100
|
+
<example>
|
|
101
|
+
<code>
|
|
102
|
+
public class OrderServiceTests : IDisposable
|
|
103
|
+
{
|
|
104
|
+
private readonly Mock<IOrderRepository> _mockRepo;
|
|
105
|
+
private readonly OrderService _service;
|
|
106
|
+
|
|
107
|
+
public OrderServiceTests()
|
|
108
|
+
{
|
|
109
|
+
// Fresh instance for each test
|
|
110
|
+
_mockRepo = new Mock<IOrderRepository>();
|
|
111
|
+
_service = new OrderService(_mockRepo.Object);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public void Dispose()
|
|
115
|
+
{
|
|
116
|
+
// Cleanup if needed
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
</code>
|
|
120
|
+
</example>
|
|
121
|
+
</test_isolation>
|
|
122
|
+
<private_methods>
|
|
123
|
+
<rule>NEVER test private methods directly</rule>
|
|
124
|
+
<why>
|
|
125
|
+
<reason>Tests should verify public behavior, not implementation</reason>
|
|
126
|
+
<reason>Makes refactoring harder</reason>
|
|
127
|
+
<reason>If private method is complex, extract to separate class</reason>
|
|
128
|
+
</why>
|
|
129
|
+
<solution>Test through public API or extract to testable class</solution>
|
|
130
|
+
</private_methods>
|
|
131
|
+
</unit_testing>
|
|
132
|
+
<mocking>
|
|
133
|
+
<library_choice>
|
|
134
|
+
<recommendation>Moq (most popular) or NSubstitute (cleaner syntax)</recommendation>
|
|
135
|
+
</library_choice>
|
|
136
|
+
<moq_patterns>
|
|
137
|
+
<setup_returns>
|
|
138
|
+
<code>
|
|
139
|
+
var mockRepo = new Mock<IUserRepository>();
|
|
140
|
+
mockRepo.Setup(r => r.GetById(1))
|
|
141
|
+
.Returns(new User { Id = 1, Name = "John" });
|
|
142
|
+
var service = new UserService(mockRepo.Object);
|
|
143
|
+
</code>
|
|
144
|
+
</setup_returns>
|
|
145
|
+
<setup_async>
|
|
146
|
+
<code>
|
|
147
|
+
mockRepo.Setup(r => r.GetByIdAsync(It.IsAny<int>()))
|
|
148
|
+
.ReturnsAsync(new User { Id = 1 });
|
|
149
|
+
</code>
|
|
150
|
+
</setup_async>
|
|
151
|
+
<verify>
|
|
152
|
+
<code>
|
|
153
|
+
// Verify method was called
|
|
154
|
+
mockRepo.Verify(r => r.Save(It.IsAny<User>()), Times.Once());
|
|
155
|
+
// Verify with specific argument
|
|
156
|
+
mockRepo.Verify(r => r.Save(It.Is<User>(u => u.Email == "test@example.com")));
|
|
157
|
+
// Verify never called
|
|
158
|
+
mockRepo.Verify(r => r.Delete(It.IsAny<int>()), Times.Never());
|
|
159
|
+
</code>
|
|
160
|
+
</verify>
|
|
161
|
+
<matchers>
|
|
162
|
+
<code>
|
|
163
|
+
It.IsAny<int>() // Any value
|
|
164
|
+
It.Is<User>(u => u.Id > 0) // Condition
|
|
165
|
+
It.IsIn(1, 2, 3) // One of values
|
|
166
|
+
It.IsRegex(@"^\d+$") // Regex match
|
|
167
|
+
</code>
|
|
168
|
+
</matchers>
|
|
169
|
+
</moq_patterns>
|
|
170
|
+
<when_to_mock>
|
|
171
|
+
<mock>External dependencies (APIs, databases, file system)</mock>
|
|
172
|
+
<mock>Slow operations</mock>
|
|
173
|
+
<mock>Non-deterministic operations (DateTime, Random)</mock>
|
|
174
|
+
<dont_mock>Value objects, DTOs, simple classes</dont_mock>
|
|
175
|
+
<dont_mock>The class under test</dont_mock>
|
|
176
|
+
</when_to_mock>
|
|
177
|
+
<best_practices>
|
|
178
|
+
<practice>Setup only what you need for the test</practice>
|
|
179
|
+
<practice>Prefer Returns over Verify when possible</practice>
|
|
180
|
+
<practice>Use strict mocks sparingly</practice>
|
|
181
|
+
<practice>Don't mock what you don't own (wrap it first)</practice>
|
|
182
|
+
</best_practices>
|
|
183
|
+
</mocking>
|
|
184
|
+
<testing_efcore>
|
|
185
|
+
<principle name="dont_mock_dbcontext" priority="critical">
|
|
186
|
+
<description>NEVER mock DbContext - use real database instead</description>
|
|
187
|
+
<why>
|
|
188
|
+
<reason>LINQ queries don't work correctly with mocks</reason>
|
|
189
|
+
<reason>Change tracking doesn't work</reason>
|
|
190
|
+
<reason>Tests pass but production fails</reason>
|
|
191
|
+
</why>
|
|
192
|
+
</principle>
|
|
193
|
+
<sqlite_inmemory recommended="true">
|
|
194
|
+
<description>Use SQLite in-memory for fast, realistic tests</description>
|
|
195
|
+
<example>
|
|
196
|
+
<code>
|
|
197
|
+
public class RepositoryTests : IDisposable
|
|
198
|
+
{
|
|
199
|
+
private readonly SqliteConnection _connection;
|
|
200
|
+
private readonly AppDbContext _context;
|
|
201
|
+
|
|
202
|
+
public RepositoryTests()
|
|
203
|
+
{
|
|
204
|
+
_connection = new SqliteConnection("DataSource=:memory:");
|
|
205
|
+
_connection.Open();
|
|
206
|
+
|
|
207
|
+
var options = new DbContextOptionsBuilder<AppDbContext>()
|
|
208
|
+
.UseSqlite(_connection)
|
|
209
|
+
.Options;
|
|
210
|
+
|
|
211
|
+
_context = new AppDbContext(options);
|
|
212
|
+
_context.Database.EnsureCreated();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
[Fact]
|
|
216
|
+
public async Task Add_ValidEntity_SavesSuccessfully()
|
|
217
|
+
{
|
|
218
|
+
var repo = new UserRepository(_context);
|
|
219
|
+
var user = new User { Email = "test@example.com" };
|
|
220
|
+
|
|
221
|
+
await repo.AddAsync(user);
|
|
222
|
+
|
|
223
|
+
var saved = await _context.Users.FindAsync(user.Id);
|
|
224
|
+
Assert.NotNull(saved);
|
|
225
|
+
Assert.Equal("test@example.com", saved.Email);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
public void Dispose()
|
|
229
|
+
{
|
|
230
|
+
_context.Dispose();
|
|
231
|
+
_connection.Dispose();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
</code>
|
|
235
|
+
</example>
|
|
236
|
+
</sqlite_inmemory>
|
|
237
|
+
<inmemory_provider>
|
|
238
|
+
<warning>Use only for simple cases - NOT a real relational database</warning>
|
|
239
|
+
<limitations>
|
|
240
|
+
<limit>No referential integrity</limit>
|
|
241
|
+
<limit>No transactions</limit>
|
|
242
|
+
<limit>Different SQL translation</limit>
|
|
243
|
+
</limitations>
|
|
244
|
+
</inmemory_provider>
|
|
245
|
+
<testcontainers>
|
|
246
|
+
<description>Use for critical integration tests with real database</description>
|
|
247
|
+
<example>
|
|
248
|
+
<code>
|
|
249
|
+
public class DatabaseFixture : IAsyncLifetime
|
|
250
|
+
{
|
|
251
|
+
private readonly MsSqlContainer _container;
|
|
252
|
+
public string ConnectionString => _container.GetConnectionString();
|
|
253
|
+
|
|
254
|
+
public DatabaseFixture()
|
|
255
|
+
{
|
|
256
|
+
_container = new MsSqlBuilder()
|
|
257
|
+
.WithImage("mcr.microsoft.com/mssql/server:2022-latest")
|
|
258
|
+
.Build();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public async Task InitializeAsync() => await _container.StartAsync();
|
|
262
|
+
public async Task DisposeAsync() => await _container.StopAsync();
|
|
263
|
+
}
|
|
264
|
+
</code>
|
|
265
|
+
</example>
|
|
266
|
+
</testcontainers>
|
|
267
|
+
</testing_efcore>
|
|
268
|
+
<integration_testing>
|
|
269
|
+
<webapplicationfactory>
|
|
270
|
+
<description>Standard for ASP.NET Core integration tests</description>
|
|
271
|
+
<example>
|
|
272
|
+
<code>
|
|
273
|
+
public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
|
|
274
|
+
{
|
|
275
|
+
private readonly HttpClient _client;
|
|
276
|
+
|
|
277
|
+
public ApiTests(WebApplicationFactory<Program> factory)
|
|
278
|
+
{
|
|
279
|
+
_client = factory.CreateClient();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
[Fact]
|
|
283
|
+
public async Task GetUsers_ReturnsSuccessStatusCode()
|
|
284
|
+
{
|
|
285
|
+
var response = await _client.GetAsync("/api/users");
|
|
286
|
+
|
|
287
|
+
response.EnsureSuccessStatusCode();
|
|
288
|
+
Assert.Equal("application/json",
|
|
289
|
+
response.Content.Headers.ContentType?.MediaType);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
</code>
|
|
293
|
+
</example>
|
|
294
|
+
</webapplicationfactory>
|
|
295
|
+
<custom_factory>
|
|
296
|
+
<description>Override services for testing</description>
|
|
297
|
+
<example>
|
|
298
|
+
<code>
|
|
299
|
+
public class CustomWebApplicationFactory<TProgram>
|
|
300
|
+
: WebApplicationFactory<TProgram> where TProgram : class
|
|
301
|
+
{
|
|
302
|
+
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
|
303
|
+
{
|
|
304
|
+
builder.ConfigureServices(services =>
|
|
305
|
+
{
|
|
306
|
+
// Replace real database with SQLite
|
|
307
|
+
var descriptor = services.SingleOrDefault(
|
|
308
|
+
d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
|
|
309
|
+
services.Remove(descriptor);
|
|
310
|
+
|
|
311
|
+
services.AddDbContext<AppDbContext>(options =>
|
|
312
|
+
options.UseSqlite("DataSource=:memory:"));
|
|
313
|
+
|
|
314
|
+
// Replace external services with fakes
|
|
315
|
+
services.AddScoped<IEmailService, FakeEmailService>();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
builder.UseEnvironment("Testing");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
</code>
|
|
322
|
+
</example>
|
|
323
|
+
</custom_factory>
|
|
324
|
+
<test_authentication>
|
|
325
|
+
<example>
|
|
326
|
+
<code>
|
|
327
|
+
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
|
328
|
+
{
|
|
329
|
+
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
|
330
|
+
{
|
|
331
|
+
var claims = new[]
|
|
332
|
+
{
|
|
333
|
+
new Claim(ClaimTypes.Name, "TestUser"),
|
|
334
|
+
new Claim(ClaimTypes.Role, "Admin")
|
|
335
|
+
};
|
|
336
|
+
var identity = new ClaimsIdentity(claims, "Test");
|
|
337
|
+
var principal = new ClaimsPrincipal(identity);
|
|
338
|
+
var ticket = new AuthenticationTicket(principal, "TestScheme");
|
|
339
|
+
|
|
340
|
+
return Task.FromResult(AuthenticateResult.Success(ticket));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
</code>
|
|
344
|
+
</example>
|
|
345
|
+
</test_authentication>
|
|
346
|
+
</integration_testing>
|
|
347
|
+
<code_coverage>
|
|
348
|
+
<philosophy>
|
|
349
|
+
<principle>Coverage is a tool, not a goal</principle>
|
|
350
|
+
<principle>Focus on critical paths, not 100%</principle>
|
|
351
|
+
<principle>Test without assertions = coverage without value</principle>
|
|
352
|
+
</philosophy>
|
|
353
|
+
<targets>
|
|
354
|
+
<target scenario="High-risk systems (financial)">80-90%</target>
|
|
355
|
+
<target scenario="Business applications">70-80%</target>
|
|
356
|
+
<target scenario="Internal tools">50-70%</target>
|
|
357
|
+
</targets>
|
|
358
|
+
<what_not_to_test>
|
|
359
|
+
<skip>Auto-generated code</skip>
|
|
360
|
+
<skip>Configuration/startup code</skip>
|
|
361
|
+
<skip>Simple DTOs</skip>
|
|
362
|
+
<skip>Third-party library wrappers</skip>
|
|
363
|
+
</what_not_to_test>
|
|
364
|
+
<tool recommendation="Coverlet">
|
|
365
|
+
<command>dotnet test --collect:"XPlat Code Coverage"</command>
|
|
366
|
+
</tool>
|
|
367
|
+
</code_coverage>
|
|
368
|
+
<theory_tests>
|
|
369
|
+
<description>Parameterized tests for multiple inputs</description>
|
|
370
|
+
<example>
|
|
371
|
+
<code>
|
|
372
|
+
[Theory]
|
|
373
|
+
[InlineData(1, 2, 3)]
|
|
374
|
+
[InlineData(-1, 1, 0)]
|
|
375
|
+
[InlineData(0, 0, 0)]
|
|
376
|
+
public void Add_VariousInputs_ReturnsCorrectSum(int a, int b, int expected)
|
|
377
|
+
{
|
|
378
|
+
var calculator = new Calculator();
|
|
379
|
+
|
|
380
|
+
var result = calculator.Add(a, b);
|
|
381
|
+
|
|
382
|
+
Assert.Equal(expected, result);
|
|
383
|
+
}
|
|
384
|
+
[Theory]
|
|
385
|
+
[MemberData(nameof(GetTestData))]
|
|
386
|
+
public void Process_TestCases_ReturnsExpected(Order order, bool expected)
|
|
387
|
+
{
|
|
388
|
+
var result = _service.IsValid(order);
|
|
389
|
+
Assert.Equal(expected, result);
|
|
390
|
+
}
|
|
391
|
+
public static IEnumerable<object[]> GetTestData()
|
|
392
|
+
{
|
|
393
|
+
yield return new object[] { new Order { Total = 100 }, true };
|
|
394
|
+
yield return new object[] { new Order { Total = 0 }, false };
|
|
395
|
+
}
|
|
396
|
+
</code>
|
|
397
|
+
</example>
|
|
398
|
+
</theory_tests>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coder-rust-async-concurrency
|
|
3
|
+
description: Rust concurrency and async best practices using std and language features. Use when writing thread-safe or async code.
|
|
4
|
+
---
|
|
5
|
+
<skill_overview>
|
|
6
|
+
<purpose>Write correct, deadlock-free concurrent Rust code</purpose>
|
|
7
|
+
<triggers>
|
|
8
|
+
<trigger>Spawning threads or tasks</trigger>
|
|
9
|
+
<trigger>Sharing state across threads</trigger>
|
|
10
|
+
<trigger>Using channels for message passing</trigger>
|
|
11
|
+
<trigger>Writing async/await code</trigger>
|
|
12
|
+
</triggers>
|
|
13
|
+
<sources>
|
|
14
|
+
<source url="https://doc.rust-lang.org/book/ch16-00-concurrency.html">The Rust Book - Concurrency</source>
|
|
15
|
+
<source url="https://doc.rust-lang.org/book/ch17-00-async-await.html">The Rust Book - Async/Await</source>
|
|
16
|
+
</sources>
|
|
17
|
+
</skill_overview>
|
|
18
|
+
<message_passing>
|
|
19
|
+
<rules>
|
|
20
|
+
<rule>Prefer channels to avoid shared mutable state</rule>
|
|
21
|
+
<rule>Move ownership through channels to enforce safety</rule>
|
|
22
|
+
</rules>
|
|
23
|
+
<example>
|
|
24
|
+
<code>
|
|
25
|
+
use std::sync::mpsc;
|
|
26
|
+
|
|
27
|
+
let (tx, rx) = mpsc::channel();
|
|
28
|
+
tx.send(42).unwrap();
|
|
29
|
+
let value = rx.recv().unwrap();
|
|
30
|
+
</code>
|
|
31
|
+
</example>
|
|
32
|
+
</message_passing>
|
|
33
|
+
<shared_state>
|
|
34
|
+
<rules>
|
|
35
|
+
<rule>Use Arc<Mutex<T>> or Arc<RwLock<T>> for shared mutable state</rule>
|
|
36
|
+
<rule>Keep lock scopes minimal and consistent</rule>
|
|
37
|
+
<rule>Handle poison errors explicitly</rule>
|
|
38
|
+
</rules>
|
|
39
|
+
</shared_state>
|
|
40
|
+
<send_sync>
|
|
41
|
+
<rules>
|
|
42
|
+
<rule>Use types that implement Send and Sync across threads</rule>
|
|
43
|
+
<rule>Avoid Rc and RefCell in multi-threaded code</rule>
|
|
44
|
+
</rules>
|
|
45
|
+
</send_sync>
|
|
46
|
+
<async_guidelines>
|
|
47
|
+
<rules>
|
|
48
|
+
<rule>Use async/await for I/O-bound work</rule>
|
|
49
|
+
<rule>Do not block inside async functions</rule>
|
|
50
|
+
<rule>Async code still needs an executor to run</rule>
|
|
51
|
+
</rules>
|
|
52
|
+
</async_guidelines>
|
|
53
|
+
<deadlocks>
|
|
54
|
+
<rules>
|
|
55
|
+
<rule>Lock in a consistent order</rule>
|
|
56
|
+
<rule>Drop guards before calling into other code</rule>
|
|
57
|
+
</rules>
|
|
58
|
+
</deadlocks>
|
|
59
|
+
<anti_patterns>
|
|
60
|
+
<avoid name="rc_in_threads">Using Rc<T> across threads</avoid>
|
|
61
|
+
<avoid name="long_lock_scope">Holding a lock across long operations</avoid>
|
|
62
|
+
<avoid name="blocking_in_async">Blocking I/O inside async code</avoid>
|
|
63
|
+
</anti_patterns>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coder-rust-axum-api
|
|
3
|
+
description: Axum Web API best practices including routing, extractors, state, middleware, and error handling. Use when building REST APIs with axum.
|
|
4
|
+
---
|
|
5
|
+
<skill_overview>
|
|
6
|
+
<purpose>Build robust, typed, and maintainable HTTP APIs with axum</purpose>
|
|
7
|
+
<triggers>
|
|
8
|
+
<trigger>Creating new axum endpoints</trigger>
|
|
9
|
+
<trigger>Designing routers and route groups</trigger>
|
|
10
|
+
<trigger>Using extractors for request data</trigger>
|
|
11
|
+
<trigger>Sharing application state safely</trigger>
|
|
12
|
+
<trigger>Implementing middleware and error mapping</trigger>
|
|
13
|
+
</triggers>
|
|
14
|
+
<sources>
|
|
15
|
+
<source url="https://github.com/tokio-rs/axum">Axum GitHub</source>
|
|
16
|
+
<source url="https://github.com/tokio-rs/axum/blob/main/axum/src/docs/extract.md">Axum Extractors Docs</source>
|
|
17
|
+
</sources>
|
|
18
|
+
</skill_overview>
|
|
19
|
+
<routing>
|
|
20
|
+
<rules>
|
|
21
|
+
<rule>Use Router::route with method routers for clarity</rule>
|
|
22
|
+
<rule>Group related endpoints with nested routers</rule>
|
|
23
|
+
<rule>Prefer typed Path and Query extractors over manual parsing</rule>
|
|
24
|
+
<rule>Use fallback to handle unknown routes consistently</rule>
|
|
25
|
+
</rules>
|
|
26
|
+
<example>
|
|
27
|
+
<code>
|
|
28
|
+
use axum::{routing::{get, post}, Router};
|
|
29
|
+
|
|
30
|
+
let app = Router::new()
|
|
31
|
+
.route("/users", get(list_users).post(create_user))
|
|
32
|
+
.route("/users/{id}", get(get_user));
|
|
33
|
+
</code>
|
|
34
|
+
</example>
|
|
35
|
+
</routing>
|
|
36
|
+
<extractors>
|
|
37
|
+
<principles>
|
|
38
|
+
<principle>Compose multiple extractors in one handler</principle>
|
|
39
|
+
<principle>Only one body-consuming extractor per handler</principle>
|
|
40
|
+
<principle>Validate and deserialize input via extractors, not manually</principle>
|
|
41
|
+
</principles>
|
|
42
|
+
<common>
|
|
43
|
+
<item>Path for route params</item>
|
|
44
|
+
<item>Query for query string</item>
|
|
45
|
+
<item>Json for JSON payloads</item>
|
|
46
|
+
<item>State for shared app state</item>
|
|
47
|
+
</common>
|
|
48
|
+
<example>
|
|
49
|
+
<code>
|
|
50
|
+
async fn handler(
|
|
51
|
+
State(state): State<AppState>,
|
|
52
|
+
Path(id): Path<u64>,
|
|
53
|
+
Query(params): Query<Pagination>
|
|
54
|
+
) { /* ... */ }
|
|
55
|
+
</code>
|
|
56
|
+
</example>
|
|
57
|
+
</extractors>
|
|
58
|
+
<state_management>
|
|
59
|
+
<rules>
|
|
60
|
+
<rule>Keep AppState cloneable and cheap to clone</rule>
|
|
61
|
+
<rule>Store shared resources behind Arc</rule>
|
|
62
|
+
<rule>Use State for app-level dependencies</rule>
|
|
63
|
+
</rules>
|
|
64
|
+
<example>
|
|
65
|
+
<code>
|
|
66
|
+
#[derive(Clone)]
|
|
67
|
+
struct AppState {
|
|
68
|
+
pool: std::sync::Arc<DbPool>,
|
|
69
|
+
}
|
|
70
|
+
</code>
|
|
71
|
+
</example>
|
|
72
|
+
</state_management>
|
|
73
|
+
<middleware>
|
|
74
|
+
<rules>
|
|
75
|
+
<rule>Apply Router::layer for global middleware</rule>
|
|
76
|
+
<rule>Use route_layer for per-route middleware</rule>
|
|
77
|
+
<rule>Prefer explicit ordering of layers</rule>
|
|
78
|
+
</rules>
|
|
79
|
+
<example>
|
|
80
|
+
<code>
|
|
81
|
+
use axum::{middleware, Router};
|
|
82
|
+
|
|
83
|
+
let app = Router::new()
|
|
84
|
+
.route("/health", get(health))
|
|
85
|
+
.route_layer(middleware::from_fn(auth_middleware));
|
|
86
|
+
</code>
|
|
87
|
+
</example>
|
|
88
|
+
</middleware>
|
|
89
|
+
<error_handling>
|
|
90
|
+
<rules>
|
|
91
|
+
<rule>Return Result from handlers and map errors to responses</rule>
|
|
92
|
+
<rule>Implement IntoResponse for custom error types</rule>
|
|
93
|
+
<rule>Never panic on expected errors</rule>
|
|
94
|
+
</rules>
|
|
95
|
+
<example>
|
|
96
|
+
<code>
|
|
97
|
+
enum AppError { NotFound, BadRequest }
|
|
98
|
+
|
|
99
|
+
impl axum::response::IntoResponse for AppError { /* map to status */ }
|
|
100
|
+
|
|
101
|
+
async fn handler() -> Result<impl axum::response::IntoResponse, AppError> { /* ... */ }
|
|
102
|
+
</code>
|
|
103
|
+
</example>
|
|
104
|
+
</error_handling>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coder-rust-conventions
|
|
3
|
+
description: Rust coding conventions, naming rules, and idiomatic patterns (rustfmt, clippy, ownership). Use when writing or reviewing Rust code.
|
|
4
|
+
---
|
|
5
|
+
<skill_overview>
|
|
6
|
+
<purpose>Write idiomatic, readable, and maintainable Rust</purpose>
|
|
7
|
+
<triggers>
|
|
8
|
+
<trigger>Writing new Rust code</trigger>
|
|
9
|
+
<trigger>Reviewing naming and style</trigger>
|
|
10
|
+
<trigger>Refactoring for idiomatic patterns</trigger>
|
|
11
|
+
<trigger>Setting formatting and lint rules</trigger>
|
|
12
|
+
</triggers>
|
|
13
|
+
<sources>
|
|
14
|
+
<source url="https://rust-lang.github.io/api-guidelines/">Rust API Guidelines</source>
|
|
15
|
+
<source url="https://rust-lang.github.io/rustfmt/">rustfmt</source>
|
|
16
|
+
<source url="https://github.com/rust-lang/rust-clippy">Clippy</source>
|
|
17
|
+
</sources>
|
|
18
|
+
</skill_overview>
|
|
19
|
+
<naming_conventions>
|
|
20
|
+
<rule name="snake_case">Functions, variables, modules, files</rule>
|
|
21
|
+
<rule name="PascalCase">Types, traits, enums, enum variants</rule>
|
|
22
|
+
<rule name="SCREAMING_SNAKE_CASE">Constants, static items</rule>
|
|
23
|
+
<rule name="as_">Use as_* for cheap conversions</rule>
|
|
24
|
+
<rule name="to_">Use to_* for potentially expensive conversions</rule>
|
|
25
|
+
</naming_conventions>
|
|
26
|
+
<formatting>
|
|
27
|
+
<rules>
|
|
28
|
+
<rule>Always run cargo fmt before commit</rule>
|
|
29
|
+
<rule>Prefer trailing commas to reduce diff noise</rule>
|
|
30
|
+
<rule>Avoid manual alignment; let rustfmt decide</rule>
|
|
31
|
+
</rules>
|
|
32
|
+
</formatting>
|
|
33
|
+
<ownership_and_borrowing>
|
|
34
|
+
<rules>
|
|
35
|
+
<rule>Prefer borrowing over cloning</rule>
|
|
36
|
+
<rule>Take &str or &[T] for read-only inputs</rule>
|
|
37
|
+
<rule>Use iterators instead of index-based loops</rule>
|
|
38
|
+
<rule>Return owned values when caller should own data</rule>
|
|
39
|
+
</rules>
|
|
40
|
+
<example>
|
|
41
|
+
<code>
|
|
42
|
+
fn find_user(name: &str, users: &[User]) -> Option<&User> { /* ... */ }
|
|
43
|
+
</code>
|
|
44
|
+
</example>
|
|
45
|
+
</ownership_and_borrowing>
|
|
46
|
+
<pattern_matching>
|
|
47
|
+
<rules>
|
|
48
|
+
<rule>Use exhaustive match for enums</rule>
|
|
49
|
+
<rule>Avoid catch-all _ unless truly unreachable</rule>
|
|
50
|
+
<rule>Use if let / while let for single-variant matches</rule>
|
|
51
|
+
</rules>
|
|
52
|
+
</pattern_matching>
|
|
53
|
+
<anti_patterns>
|
|
54
|
+
<avoid name="unwrap_in_prod">Avoid unwrap/expect in non-test code</avoid>
|
|
55
|
+
<avoid name="clone_everywhere">Do not clone to silence borrow checker</avoid>
|
|
56
|
+
<avoid name="large_mutable_state">Avoid large mutable shared state</avoid>
|
|
57
|
+
</anti_patterns>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coder-rust-error-handling
|
|
3
|
+
description: Rust error handling, validation, and Result best practices. Use when designing error flows or mapping errors across boundaries.
|
|
4
|
+
---
|
|
5
|
+
<skill_overview>
|
|
6
|
+
<purpose>Design predictable and ergonomic error handling in Rust</purpose>
|
|
7
|
+
<triggers>
|
|
8
|
+
<trigger>Designing error types</trigger>
|
|
9
|
+
<trigger>Propagating errors with Result</trigger>
|
|
10
|
+
<trigger>Mapping errors at API boundaries</trigger>
|
|
11
|
+
<trigger>Choosing between panic and Result</trigger>
|
|
12
|
+
</triggers>
|
|
13
|
+
<sources>
|
|
14
|
+
<source url="https://doc.rust-lang.org/book/ch09-00-error-handling.html">The Rust Book - Error Handling</source>
|
|
15
|
+
</sources>
|
|
16
|
+
</skill_overview>
|
|
17
|
+
<panic_vs_result>
|
|
18
|
+
<use_panic_when>
|
|
19
|
+
<case>Unrecoverable, invariant-breaking failures</case>
|
|
20
|
+
<case>Bug in the program (logic error)</case>
|
|
21
|
+
</use_panic_when>
|
|
22
|
+
<use_result_when>
|
|
23
|
+
<case>Expected errors (not found, validation)</case>
|
|
24
|
+
<case>I/O or external dependency failures</case>
|
|
25
|
+
</use_result_when>
|
|
26
|
+
</panic_vs_result>
|
|
27
|
+
<error_types>
|
|
28
|
+
<rules>
|
|
29
|
+
<rule>Define a dedicated error enum for a module or service</rule>
|
|
30
|
+
<rule>Implement Display and Error for custom errors</rule>
|
|
31
|
+
<rule>Use From to convert lower-level errors</rule>
|
|
32
|
+
</rules>
|
|
33
|
+
<example>
|
|
34
|
+
<code>
|
|
35
|
+
enum ServiceError { NotFound, InvalidInput, Io(std::io::Error) }
|
|
36
|
+
</code>
|
|
37
|
+
</example>
|
|
38
|
+
</error_types>
|
|
39
|
+
<propagation>
|
|
40
|
+
<rules>
|
|
41
|
+
<rule>Use ? to propagate errors</rule>
|
|
42
|
+
<rule>Add context with map_err or custom variants</rule>
|
|
43
|
+
<rule>Preserve original error for debugging</rule>
|
|
44
|
+
</rules>
|
|
45
|
+
</propagation>
|
|
46
|
+
<boundary_mapping>
|
|
47
|
+
<rules>
|
|
48
|
+
<rule>Map internal errors to public error codes/messages</rule>
|
|
49
|
+
<rule>Do not leak sensitive details to clients</rule>
|
|
50
|
+
</rules>
|
|
51
|
+
</boundary_mapping>
|
|
52
|
+
<anti_patterns>
|
|
53
|
+
<avoid name="unwrap_in_flow">Avoid unwrap/expect for normal flow</avoid>
|
|
54
|
+
<avoid name="string_errors">Avoid plain String errors without structure</avoid>
|
|
55
|
+
<avoid name="panic_for_validation">Do not panic on validation failures</avoid>
|
|
56
|
+
</anti_patterns>
|