From zenbu-powers
當在 Gherkin 中撰寫寫入操作步驟(Given 已完成 / When 執行中),務必參考此規範。 使用 HttpClient 發送 HTTP POST/PUT/PATCH/DELETE 請求。
npx claudepluginhub zenbuapps/zenbu-powers --plugin zenbu-powersThis skill uses the workspace's default tool permissions.
使用 HttpClient(來自 WebApplicationFactory)發送 HTTP 寫入請求,執行系統狀態變更操作。
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 寫入請求,執行系統狀態變更操作。
| 項目 | 技術 |
|---|---|
| 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 |
Given/When 語句執行寫入操作(Command)。
識別規則:
通用判斷:如果語句是修改系統狀態的操作且不需要回傳值,就使用此 Handler。
| Command | Query | |
|---|---|---|
| HTTP 方法 | POST / PUT / PATCH / DELETE | GET |
| 系統狀態 | 修改 | 不修改 |
| Response 驗證 | 不驗證(交給 Then) | 不驗證(交給 Then) |
| 用途 | 執行操作 | 讀取資料 |
| Given + Command | When + Command | |
|---|---|---|
| 目的 | 建立前置資料(透過 API) | 測試目標操作 |
| 失敗處理 | 不預期失敗 | 可能成功或失敗 |
Ids 字典取得用戶 ID(Ids[userName].ToString()!)_jwtHelper.GenerateToken(userId) 產生 JWT Tokenapi.yml schemas)JsonSerializer.Serialize() 序列化為 JSONStringContent(指定 application/json)Authorization header(Bearer token)PostAsync / PutAsync / PatchAsync / DeleteAsync)_ctx["LastResponse"][Binding]
public class CommandSteps
{
private readonly ScenarioContext _ctx;
private readonly HttpClient _client;
private readonly AppDbContext _dbContext;
private readonly JwtHelper _jwtHelper;
public CommandSteps(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 WhenUpdateProgress(string userName, int lessonId, int progress)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
var requestBody = new { lessonId, progress };
var content = new StringContent(
JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json");
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _client.PostAsync(
"/api/v1/lesson-progress/update-video-progress", content);
_ctx["LastResponse"] = response;
}
[When(@"用戶 ""(.*)"" 批量更新以下商品數量:")]
public async Task WhenBatchUpdate(string userName, Table table)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
var items = table.Rows.Select(row => new {
productId = row["productId"],
quantity = int.Parse(row["quantity"])
}).ToList();
var content = new StringContent(
JsonSerializer.Serialize(new { items }), Encoding.UTF8, "application/json");
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _client.PostAsync("/api/v1/cart/batch-update", content);
_ctx["LastResponse"] = response;
}
[Given(@"用戶 ""(.*)"" 已訂閱課程 (.*)")]
public async Task GivenUserSubscribed(string userName, int courseId)
{
var userId = Ids[userName].ToString()!;
var token = _jwtHelper.GenerateToken(userId);
var requestBody = new { courseId };
var content = new StringContent(
JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json");
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _client.PostAsync("/api/v1/subscriptions", content);
_ctx["LastResponse"] = response;
}
| 項目 | 模式 |
|---|---|
| HTTP Client | _client(HttpClient,來自 WebApplicationFactory) |
| Auth | _jwtHelper.GenerateToken(userId) → new AuthenticationHeaderValue("Bearer", token) |
| POST | await _client.PostAsync(url, content) |
| PUT | await _client.PutAsync(url, content) |
| PATCH | await _client.PatchAsync(url, content) |
| DELETE | await _client.DeleteAsync(url) |
| Path Params | C# 字串插值:$"/api/v1/lessons/{lessonId}" |
| Request Body | JsonSerializer.Serialize(new { ... }) → new StringContent(..., Encoding.UTF8, "application/json") |
| Response 儲存 | _ctx["LastResponse"] = response |
var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/v1/items/{itemId}")
{
Content = new StringContent(
JsonSerializer.Serialize(requestBody), Encoding.UTF8, "application/json")
};
var response = await _client.SendAsync(request);
_ctx["LastResponse"] = response;
_ctx["LastResponse"],assertion 交給 Then handlerapi.yml schemas 一致(camelCase)_ctx["LastResponse"] = response,型別為 HttpResponseMessageIds[userName].ToString()!,不硬編碼 ID