aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend/ratelimit/ratelimit_test.go
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-16 20:05:39 +0900
committernsfisis <nsfisis@gmail.com>2026-02-16 20:13:47 +0900
commit071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d (patch)
treed0174928c0d320bb76e0cdb899beee0476643d55 /backend/ratelimit/ratelimit_test.go
parent5ed369a6c70707543fd5ec9a13c79851fdfc5d6c (diff)
downloadphperkaigi-2026-albatross-071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d.tar.gz
phperkaigi-2026-albatross-071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d.tar.zst
phperkaigi-2026-albatross-071e7cc78d3f13fa782dbc6ca5fcec3a37263a4d.zip
test(backend): add unit tests for auth, config, ratelimit, game, and api
Cover previously untested logic: session ID generation/hashing, password authentication, IP rate limiting, game state helpers, handler endpoints, task enqueue/result processing, and config loading. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'backend/ratelimit/ratelimit_test.go')
-rw-r--r--backend/ratelimit/ratelimit_test.go123
1 files changed, 123 insertions, 0 deletions
diff --git a/backend/ratelimit/ratelimit_test.go b/backend/ratelimit/ratelimit_test.go
new file mode 100644
index 0000000..65878c4
--- /dev/null
+++ b/backend/ratelimit/ratelimit_test.go
@@ -0,0 +1,123 @@
+package ratelimit
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/labstack/echo/v4"
+ "golang.org/x/time/rate"
+)
+
+func TestGetLimiter_SameIP(t *testing.T) {
+ rl := &IPRateLimiter{
+ rate: rate.Limit(10),
+ burst: 1,
+ }
+
+ l1 := rl.getLimiter("192.168.1.1")
+ l2 := rl.getLimiter("192.168.1.1")
+ if l1 != l2 {
+ t.Error("expected same limiter for same IP")
+ }
+}
+
+func TestGetLimiter_DifferentIP(t *testing.T) {
+ rl := &IPRateLimiter{
+ rate: rate.Limit(10),
+ burst: 1,
+ }
+
+ l1 := rl.getLimiter("192.168.1.1")
+ l2 := rl.getLimiter("192.168.1.2")
+ if l1 == l2 {
+ t.Error("expected different limiters for different IPs")
+ }
+}
+
+func TestLoginRateLimitMiddleware_AllowsNonLogin(t *testing.T) {
+ rl := &IPRateLimiter{
+ rate: rate.Limit(0), // zero rate = deny all
+ burst: 0,
+ }
+
+ e := echo.New()
+ req := httptest.NewRequest(http.MethodGet, "/api/games", nil)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(req, rec)
+ c.SetPath("/api/games")
+
+ handler := LoginRateLimitMiddleware(rl)(func(c echo.Context) error {
+ return c.NoContent(http.StatusOK)
+ })
+
+ if err := handler(c); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if rec.Code != http.StatusOK {
+ t.Errorf("expected 200, got %d", rec.Code)
+ }
+}
+
+func TestLoginRateLimitMiddleware_BlocksExcessiveLogin(t *testing.T) {
+ rl := &IPRateLimiter{
+ rate: rate.Limit(0.001), // very low rate
+ burst: 1,
+ }
+
+ e := echo.New()
+
+ // First request should succeed (burst = 1)
+ req1 := httptest.NewRequest(http.MethodPost, "/api/login", nil)
+ rec1 := httptest.NewRecorder()
+ c1 := e.NewContext(req1, rec1)
+ c1.SetPath("/api/login")
+
+ handler := LoginRateLimitMiddleware(rl)(func(c echo.Context) error {
+ return c.NoContent(http.StatusOK)
+ })
+
+ if err := handler(c1); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if rec1.Code != http.StatusOK {
+ t.Errorf("first request: expected 200, got %d", rec1.Code)
+ }
+
+ // Second request should be rate limited
+ req2 := httptest.NewRequest(http.MethodPost, "/api/login", nil)
+ rec2 := httptest.NewRecorder()
+ c2 := e.NewContext(req2, rec2)
+ c2.SetPath("/api/login")
+
+ if err := handler(c2); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if rec2.Code != http.StatusTooManyRequests {
+ t.Errorf("second request: expected 429, got %d", rec2.Code)
+ }
+}
+
+func TestLoginRateLimitMiddleware_AllowsNonPostLogin(t *testing.T) {
+ rl := &IPRateLimiter{
+ rate: rate.Limit(0),
+ burst: 0,
+ }
+
+ e := echo.New()
+ req := httptest.NewRequest(http.MethodGet, "/api/login", nil)
+ rec := httptest.NewRecorder()
+ c := e.NewContext(req, rec)
+ c.SetPath("/api/login")
+
+ handler := LoginRateLimitMiddleware(rl)(func(c echo.Context) error {
+ return c.NoContent(http.StatusOK)
+ })
+
+ if err := handler(c); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if rec.Code != http.StatusOK {
+ t.Errorf("expected 200 for GET /login, got %d", rec.Code)
+ }
+}