Files
Jiayuan Zhangandmultica-agent 8ade96e671 MUL-6584: establish Public API v1 foundation (#7450)
* feat(api): establish public v1 contract foundation

Co-authored-by: multica-agent <[email protected]>

* fix(plugins): close public API contract gaps

Co-authored-by: multica-agent <[email protected]>

* fix(issues): enforce revision guard during update

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-08-23 22:47:37 +08:00

66 lines
2.2 KiB
Go

package middleware
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
publicapiv1 "github.com/multica-ai/multica/server/pkg/publicapi/v1"
)
func TestPluginRateLimitIsPerCredentialAndUsesStableProblem(t *testing.T) {
rdb := newRedisTestClient(t)
handler := PluginRateLimit(rdb, 1, time.Minute)(okHandler)
call := func(token string) *httptest.ResponseRecorder {
request := httptest.NewRequest(http.MethodGet, "/v1/issues/MUL-1", nil)
request.Header.Set("Authorization", "Bearer "+token)
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
return response
}
if response := call("mpi_first-secret"); response.Code != http.StatusOK {
t.Fatalf("first credential call status = %d", response.Code)
}
limited := call("mpi_first-secret")
if limited.Code != http.StatusTooManyRequests || limited.Header().Get("Retry-After") != "60" {
t.Fatalf("limited response status=%d retry-after=%q body=%s", limited.Code, limited.Header().Get("Retry-After"), limited.Body.String())
}
var problem publicapiv1.Problem
if err := json.Unmarshal(limited.Body.Bytes(), &problem); err != nil {
t.Fatalf("decode problem: %v", err)
}
if problem.Code != "rate_limited" || problem.RequestID == "" || problem.Error == "" {
t.Fatalf("unexpected problem: %+v", problem)
}
if response := call("mpi_second-secret"); response.Code != http.StatusOK {
t.Fatalf("different credential shared budget: status=%d", response.Code)
}
keys, err := rdb.Keys(context.Background(), "mul:ratelimit:plugin:*").Result()
if err != nil {
t.Fatalf("list limiter keys: %v", err)
}
for _, key := range keys {
if strings.Contains(key, "first-secret") || strings.Contains(key, "second-secret") {
t.Fatalf("credential leaked into rate-limit key: %q", key)
}
}
}
func TestPluginRateLimitWithoutRedisFailsOpen(t *testing.T) {
handler := PluginRateLimit(nil, 1, time.Minute)(okHandler)
request := httptest.NewRequest(http.MethodGet, "/v1/context", nil)
request.Header.Set("Authorization", "Bearer mpi_local")
response := httptest.NewRecorder()
handler.ServeHTTP(response, request)
if response.Code != http.StatusOK {
t.Fatalf("nil Redis status = %d", response.Code)
}
}