From zenbu-powers
當在 Gherkin 中撰寫 Query 操作步驟時,務必參考此規範。 使用 HttpClient 發送 HTTP GET 請求。
npx claudepluginhub zenbuapps/zenbu-powers --plugin zenbu-powersThis skill uses the workspace's default tool permissions.
使用 HttpClient(來自 WebApplicationFactory)發送 HTTP GET 請求,執行資料讀取操作。
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Share bugs, ideas, or general feedback.
使用 HttpClient(來自 WebApplicationFactory)發送 HTTP GET 請求,執行資料讀取操作。
| 項目 | 技術 |
|---|---|
| Language | C# 12 / .NET 8+ |
| BDD | SpecFlow 3.9+(Cucumber Expressions) |
| HTTP Client | WebApplicationFactory<Program> + HttpClient |
| ORM | Entity Framework Core 8+ |
| Database | PostgreSQL 16(Testcontainers for .NET) |
| DI | Microsoft.Extensions.DependencyInjection(BoDi for SpecFlow) |
| Assertion | FluentAssertions 6+ |
| JSON | System.Text.Json |
| Auth | JWT(System.IdentityModel.Tokens.Jwt) |
| Test Runner | xUnit |
| Test Command | dotnet test --filter "Category!=Ignore" |
| Red Failure | HTTP 404 Not Found |
When 語句執行讀取操作(Query)。
識別規則:
通用判斷:如果 When 是讀取操作且需要回傳值供 Then 驗證,就使用此 Handler。
| Query | Command | |
|---|---|---|
| HTTP 方法 | GET | POST / PUT / PATCH / DELETE |
| 系統狀態 | 不修改 | 修改 |
| Then 搭配 | readmodel-then | success-failure / aggregate-then |
Ids 字典取得用戶 ID(Ids[userName].ToString()!)_jwtHelper.GenerateToken(userId) 產生 JWT TokenAuthorization header(Bearer token)await _client.GetAsync(url))_ctx["LastResponse"][Binding]
public class QuerySteps
{
private readonly ScenarioContext _ctx;
private readonly HttpClient _client;
private readonly AppDbContext _dbContext;
private readonly JwtHelper _jwtHelper;
public QuerySteps(ScenarioContext ctx)
{
_ctx = ctx;
_client = ctx.Get<HttpClient>("HttpClient");
_dbContext = ctx.Get<AppDbContext>("DbContext");
_jwtHelper = ctx.Get<JwtHelper>("JwtHelper");
}
private Dictionary<string, object> Ids => _ctx.Get<Dictionary<string, object>>("Ids");
}
[When(@"用戶 ""(.*)"" 查詢課程 (.*) 的進度")]
public async Task WhenQueryProgress(string userName, int lessonId)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _client.GetAsync($"/api/v1/lessons/{lessonId}/progress");
_ctx["LastResponse"] = response;
}
使用 QueryHelpers.AddQueryString() 構建帶查詢參數的 URL(來自 Microsoft.AspNetCore.WebUtilities)。
[When(@"用戶 ""(.*)"" 查詢旅程 (.*) 中第 (.*) 章的所有課程")]
public async Task WhenQueryLessonsByChapter(string userName, int journeyId, int chapterId)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var url = QueryHelpers.AddQueryString(
$"/api/v1/journeys/{journeyId}/lessons",
new Dictionary<string, string?> { ["chapterId"] = chapterId.ToString() });
var response = await _client.GetAsync(url);
_ctx["LastResponse"] = response;
}
[When(@"用戶 ""(.*)"" 查詢購物車中的所有商品")]
public async Task WhenQueryCartItems(string userName)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _client.GetAsync("/api/v1/cart/items");
_ctx["LastResponse"] = response;
}
[When(@"用戶 ""(.*)"" 搜尋價格 (.*) 到 (.*) 的商品")]
public async Task WhenSearchProducts(string userName, int minPrice, int maxPrice)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var url = QueryHelpers.AddQueryString("/api/v1/products/search",
new Dictionary<string, string?>
{
["minPrice"] = minPrice.ToString(),
["maxPrice"] = maxPrice.ToString()
});
var response = await _client.GetAsync(url);
_ctx["LastResponse"] = response;
}
| 項目 | 模式 |
|---|---|
| HTTP Client | _client(HttpClient,來自 WebApplicationFactory) |
| Auth | _jwtHelper.GenerateToken(userId) → new AuthenticationHeaderValue("Bearer", token) |
| GET | await _client.GetAsync(url) |
| Path Params | C# 字串插值:$"/api/v1/lessons/{lessonId}/progress" |
| Query Params | QueryHelpers.AddQueryString(basePath, paramDict) |
| Response 儲存 | _ctx["LastResponse"] = response |
$"/api/v1/lessons/{lessonId}"QueryHelpers.AddQueryString() 安全附加,自動處理 URL encodingapi.yml paths 讀取,不自行編造_ctx["LastResponse"] = response,型別為 HttpResponseMessageapi.yml 讀取,不自行編造端點Ids[userName].ToString()!,不硬編碼 ID