Repository navigation
Conversation
The IpBasedTokenBucket endpoint policy never applied. UseRateLimiter runs before UseRouting, so no endpoint metadata exists yet when the middleware looks up the policy. Chain the per-IP token bucket ahead of the concurrency limiter in GlobalLimiter, as ADR 0001 describes, and drop the RequireRateLimiting calls on /graphql and /health. The limiter builders and limits are internal so tests can reuse them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Disable xUnit parallelization so scenarios do not compete for CPU. Configure a known ApiKey:Value in the load test host and send it from BaseLoadTest, because every GraphQL request without it was rejected. Point Hangfire storage at the test container and drop hosted services so the job server never starts or touches LocalDB. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GraphQLLoadStep runs a named operation and records the HTTP status or GraphQL error code, the payload size and an optional body check. LoadThresholds defines error budget, tail latency, status mix, payload guard and step health once, and applies them both as NBomber runtime thresholds and as a post-run assertion. LoadTestBudgets holds limits taken from a measured baseline run. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A shared base replaces the limiter configuration and sets the rejection status to 429. Separate factories keep only the per-IP token bucket, only the concurrency limiter, or no limiter, so a test can only be rejected by the limiter it targets. The concurrency factory delays DbContext creation to hold permits while a request is processed. Expose internals to the load tests so they build limiters from the production code. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Use business-named steps and apply all five threshold types. Latency and ramp tests use an open arrival-rate model (Inject and RampingInject) so a slow server shows up as latency instead of fewer requests. The ramp test names steps by phase to get per-phase stats and checks recovery after ramping back down. The concurrency test no longer sleeps inside the scenario and asserts that OK requests exceed the token bucket size. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Record the in-process host, open load model, serialized execution and measured budgets. Update CLAUDE.md for the chained global rate limiter and the load test command. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
9 tasks done
The integration suite shares one factory, so every test draws from a single per-IP token bucket. Now that the bucket is enforced through the global limiter, the suite runs out of tokens on a fast machine and roughly 110 tests fail with 429s. None of these tests cover rate limiting, so remove the limiter from the test host, the same way the load test factories do. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Dejmenek
added this pull request to stack #168
September 29, 2026 14:09
Dejmenek
marked this pull request as ready for review
September 29, 2026 19:43
Dejmenek
deleted the
test/165-improve-load-test-quality-and-expand-scenario-coverage
branch
September 29, 2026 19:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is the first PR for #165. It fixes the existing load tests and adds the shared pieces the later PRs build on. It also fixes a production bug the load tests exposed: the per-IP rate limiter never took effect.
Rate limiter fix
The
IpBasedTokenBucketendpoint policy was never applied.UseRateLimiterruns beforeUseRouting, so the middleware has no endpoint to read the policy from. The token bucket test made this visible: 600 of 600 requests returned 200.The per-IP token bucket now sits in
GlobalLimiter, ahead of the concurrency limiter, as ADR 0001 describes. TheRequireRateLimitingcalls on/graphqland/healthare removed.This changes behavior. Every endpoint, including
/health, now enforces 100 burst requests and then 50 per minute per IP. That is stricter than what clients get today, so the numbers deserve a look.Load test changes
GraphQLLoadStep,LoadThresholdsandLoadTestBudgetsadd business-named steps and the five threshold types: error budget, tail latency, status mix, payload guard and step health. Limits come from a measured baseline, described in ADR 0008.Testing
Unit tests: 160 passed. Integration tests: 252 passed. Load tests: 4 of 4 passed. The load tests need Docker and run manually with
dotnet test TournamentAPI.LoadTests --filter "Category=Load".What comes next
The remaining PRs in the stack add dataset and auth scaffolding, write and contention scenarios, and spike, soak and breakpoint tests.
Part of #165.
Stack created with GitHub Stacks CLI • Give Feedback 💬
🤖 Generated with Claude Code