Add user RPM and concurrency middleware, enforce channel concurrency for relay selection and retry paths, and wire checks into relay routes.
138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/QuantumNous/new-api/model"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func restoreConcurrencyStubs(t *testing.T) {
|
|
t.Helper()
|
|
|
|
previousGetUser := getUserByIdForConcurrency
|
|
previousCheckRPM := checkUserRPMForConcurrency
|
|
previousAcquire := acquireUserConcurrencyForCheck
|
|
t.Cleanup(func() {
|
|
getUserByIdForConcurrency = previousGetUser
|
|
checkUserRPMForConcurrency = previousCheckRPM
|
|
acquireUserConcurrencyForCheck = previousAcquire
|
|
})
|
|
}
|
|
|
|
func performConcurrencyRequest(handler gin.HandlerFunc, userId int) *httptest.ResponseRecorder {
|
|
gin.SetMode(gin.TestMode)
|
|
router := gin.New()
|
|
router.GET("/test", func(c *gin.Context) {
|
|
if userId > 0 {
|
|
c.Set("id", userId)
|
|
}
|
|
}, handler, func(c *gin.Context) {
|
|
c.Status(http.StatusNoContent)
|
|
})
|
|
|
|
recorder := httptest.NewRecorder()
|
|
request := httptest.NewRequest(http.MethodGet, "/test", nil)
|
|
router.ServeHTTP(recorder, request)
|
|
return recorder
|
|
}
|
|
|
|
func TestConcurrencyCheckAllowsWhenUserMissingFromContext(t *testing.T) {
|
|
restoreConcurrencyStubs(t)
|
|
|
|
getUserByIdForConcurrency = func(id int, selectAll bool) (*model.User, error) {
|
|
t.Fatalf("expected user lookup to be skipped, got id %d", id)
|
|
return nil, nil
|
|
}
|
|
|
|
recorder := performConcurrencyRequest(ConcurrencyCheck(), 0)
|
|
|
|
require.Equal(t, http.StatusNoContent, recorder.Code)
|
|
}
|
|
|
|
func TestConcurrencyCheckFailOpenWhenUserLookupFails(t *testing.T) {
|
|
restoreConcurrencyStubs(t)
|
|
|
|
getUserByIdForConcurrency = func(id int, selectAll bool) (*model.User, error) {
|
|
return nil, errors.New("database unavailable")
|
|
}
|
|
|
|
recorder := performConcurrencyRequest(ConcurrencyCheck(), 1)
|
|
|
|
require.Equal(t, http.StatusNoContent, recorder.Code)
|
|
}
|
|
|
|
func TestConcurrencyCheckAllowsWhenLimitsDisabled(t *testing.T) {
|
|
restoreConcurrencyStubs(t)
|
|
|
|
getUserByIdForConcurrency = func(id int, selectAll bool) (*model.User, error) {
|
|
return &model.User{Id: id}, nil
|
|
}
|
|
checkUserRPMForConcurrency = func(userId, limit int) bool {
|
|
t.Fatalf("expected RPM check to be skipped")
|
|
return false
|
|
}
|
|
acquireUserConcurrencyForCheck = func(userId, limit int) (bool, func()) {
|
|
t.Fatalf("expected concurrency acquisition to be skipped")
|
|
return false, func() {}
|
|
}
|
|
|
|
recorder := performConcurrencyRequest(ConcurrencyCheck(), 1)
|
|
|
|
require.Equal(t, http.StatusNoContent, recorder.Code)
|
|
}
|
|
|
|
func TestConcurrencyCheckRejectsWhenRPMExceeded(t *testing.T) {
|
|
restoreConcurrencyStubs(t)
|
|
|
|
getUserByIdForConcurrency = func(id int, selectAll bool) (*model.User, error) {
|
|
return &model.User{Id: id, RpmLimit: 1}, nil
|
|
}
|
|
checkUserRPMForConcurrency = func(userId, limit int) bool {
|
|
return false
|
|
}
|
|
|
|
recorder := performConcurrencyRequest(ConcurrencyCheck(), 1)
|
|
|
|
require.Equal(t, http.StatusTooManyRequests, recorder.Code)
|
|
}
|
|
|
|
func TestConcurrencyCheckRejectsWhenUserConcurrencyExceeded(t *testing.T) {
|
|
restoreConcurrencyStubs(t)
|
|
|
|
getUserByIdForConcurrency = func(id int, selectAll bool) (*model.User, error) {
|
|
return &model.User{Id: id, ConcurrencyLimit: 1}, nil
|
|
}
|
|
acquireUserConcurrencyForCheck = func(userId, limit int) (bool, func()) {
|
|
return false, func() {}
|
|
}
|
|
|
|
recorder := performConcurrencyRequest(ConcurrencyCheck(), 1)
|
|
|
|
require.Equal(t, http.StatusTooManyRequests, recorder.Code)
|
|
}
|
|
|
|
func TestConcurrencyCheckReleasesAfterRequest(t *testing.T) {
|
|
restoreConcurrencyStubs(t)
|
|
|
|
released := false
|
|
getUserByIdForConcurrency = func(id int, selectAll bool) (*model.User, error) {
|
|
return &model.User{Id: id, ConcurrencyLimit: 1}, nil
|
|
}
|
|
acquireUserConcurrencyForCheck = func(userId, limit int) (bool, func()) {
|
|
return true, func() {
|
|
released = true
|
|
}
|
|
}
|
|
|
|
recorder := performConcurrencyRequest(ConcurrencyCheck(), 1)
|
|
|
|
require.Equal(t, http.StatusNoContent, recorder.Code)
|
|
require.True(t, released)
|
|
}
|