feat(management): add usage record retrieval endpoint

- Implemented `/v0/management/usage` endpoint for fetching queued usage records from Redis.
- Included validation for `count` parameter to ensure positive integers.
- Added unit tests for queue retrieval and validation, with authentication validation in integration tests.
- Updated management routing to include the new endpoint.
This commit is contained in:
Luis Pater
2026-05-05 02:53:04 +08:00
parent ba5d8ca733
commit 61b39d49bd
4 changed files with 209 additions and 0 deletions
+55
View File
@@ -13,6 +13,7 @@ import (
gin "github.com/gin-gonic/gin"
proxyconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
@@ -84,6 +85,60 @@ func TestHealthz(t *testing.T) {
})
}
func TestManagementUsageRequiresManagementAuthAndPopsArray(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")
prevQueueEnabled := redisqueue.Enabled()
redisqueue.SetEnabled(false)
t.Cleanup(func() {
redisqueue.SetEnabled(false)
redisqueue.SetEnabled(prevQueueEnabled)
})
server := newTestServer(t)
redisqueue.Enqueue([]byte(`{"id":1}`))
redisqueue.Enqueue([]byte(`{"id":2}`))
missingKeyReq := httptest.NewRequest(http.MethodGet, "/v0/management/usage?count=2", nil)
missingKeyRR := httptest.NewRecorder()
server.engine.ServeHTTP(missingKeyRR, missingKeyReq)
if missingKeyRR.Code != http.StatusUnauthorized {
t.Fatalf("missing key status = %d, want %d body=%s", missingKeyRR.Code, http.StatusUnauthorized, missingKeyRR.Body.String())
}
authReq := httptest.NewRequest(http.MethodGet, "/v0/management/usage?count=2", nil)
authReq.Header.Set("Authorization", "Bearer test-management-key")
authRR := httptest.NewRecorder()
server.engine.ServeHTTP(authRR, authReq)
if authRR.Code != http.StatusOK {
t.Fatalf("authenticated status = %d, want %d body=%s", authRR.Code, http.StatusOK, authRR.Body.String())
}
var payload []json.RawMessage
if errUnmarshal := json.Unmarshal(authRR.Body.Bytes(), &payload); errUnmarshal != nil {
t.Fatalf("unmarshal response: %v body=%s", errUnmarshal, authRR.Body.String())
}
if len(payload) != 2 {
t.Fatalf("response records = %d, want 2", len(payload))
}
for i, raw := range payload {
var record struct {
ID int `json:"id"`
}
if errUnmarshal := json.Unmarshal(raw, &record); errUnmarshal != nil {
t.Fatalf("unmarshal record %d: %v", i, errUnmarshal)
}
if record.ID != i+1 {
t.Fatalf("record %d id = %d, want %d", i, record.ID, i+1)
}
}
if remaining := redisqueue.PopOldest(1); len(remaining) != 0 {
t.Fatalf("remaining queue = %q, want empty", remaining)
}
}
func TestAmpProviderModelRoutes(t *testing.T) {
testCases := []struct {
name string