package scenario import ( "context" "sync" "time" "scrabble/loadtest/internal/edge" "scrabble/loadtest/internal/seed" ) // HammerConfig parameterises the gateway-hammer: how many concurrent callers and for // how long to deliberately exceed the per-user rate limit from a single account. type HammerConfig struct { Workers int Duration time.Duration } // DefaultHammer returns a hammer that comfortably exceeds the 300/min per-user limit. func DefaultHammer() HammerConfig { return HammerConfig{Workers: 20, Duration: 15 * time.Second} } // Hammer drives games.list from a single account far above the per-user rate limit to // verify the limiter holds — rejections surface as the "rate_limited" code — and to // measure its cost. Every call is recorded under "hammer:games.list" so the report // shows the ok/rate_limited split and the rejection latency separately from the // realistic traffic. func (d *Driver) Hammer(ctx context.Context, acc seed.Account, cfg HammerConfig) { runCtx, cancel := context.WithTimeout(ctx, cfg.Duration) defer cancel() d.log.Info("gateway-hammer", "workers", cfg.Workers, "duration", cfg.Duration) c := edge.New(d.gateway) var wg sync.WaitGroup for w := 0; w < cfg.Workers; w++ { wg.Add(1) go func() { defer wg.Done() for runCtx.Err() == nil { t0 := time.Now() _, code, _ := c.GamesList(runCtx, acc.Token) d.rec.Record("hammer:games.list", code, time.Since(t0)) } }() } wg.Wait() }