Files
sub2api-cn-relay-manager/internal/app/route_logging_api_test.go
2026-05-28 21:24:05 +08:00

146 lines
4.7 KiB
Go

package app
import (
"context"
"net/http"
"path/filepath"
"testing"
)
func TestAPIAppendRouteDecisionLogReturnsCreated(t *testing.T) {
handler := NewAPIHandler("secret-token", ActionSet{
AppendRouteDecisionLog: func(_ context.Context, req AppendRouteDecisionLogRequest) (RouteDecisionLogInfo, error) {
if req.RequestID != "req-1" {
t.Fatalf("RequestID = %q, want req-1", req.RequestID)
}
return RouteDecisionLogInfo{
ID: 1,
RequestID: req.RequestID,
LogicalGroupID: req.LogicalGroupID,
PublicModel: req.PublicModel,
SelectedRouteID: req.SelectedRouteID,
SelectedShadowGroupID: req.SelectedShadowGroupID,
}, nil
},
})
request := httptestRequest(t, http.MethodPost, "/api/routing/logs/decisions", map[string]any{
"request_id": "req-1",
"logical_group_id": "gpt-shared",
"public_model": "gpt-5.4",
"selected_route_id": "asxs",
"selected_shadow_group_id": "gpt-shared__asxs",
"sync": true,
}, "secret-token")
response := httptestRecorder(handler, request)
assertStatusCode(t, response, http.StatusCreated)
assertJSONContains(t, response.Body().Bytes(), "decision_log.request_id", "req-1")
}
func TestAPIListRouteDecisionLogsRejectsInvalidLimit(t *testing.T) {
handler := NewAPIHandler("secret-token", ActionSet{
ListRouteDecisionLogs: func(context.Context, ListRouteDecisionLogsRequest) ([]RouteDecisionLogInfo, error) {
t.Fatal("ListRouteDecisionLogs should not be called")
return nil, nil
},
})
request := httptestRequest(t, http.MethodGet, "/api/routing/logs/decisions?limit=zero", nil, "secret-token")
response := httptestRecorder(handler, request)
assertStatusCode(t, response, http.StatusBadRequest)
}
func TestNewActionSetRouteLoggingFlow(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "route-logging.db")
dsn := "file:" + filepath.ToSlash(dbPath) + "?_busy_timeout=5000"
actions := NewActionSet(dsn)
ctx := context.Background()
decision, err := actions.AppendRouteDecisionLog(ctx, AppendRouteDecisionLogRequest{
RequestID: "req-1",
LogicalGroupID: "gpt-shared",
PublicModel: "gpt-5.4",
StickyKey: "sticky-1",
StickyKeyType: "conversation",
StickyHit: true,
SelectedRouteID: "asxs",
SelectedShadowGroupID: "gpt-shared__asxs",
UpstreamStatus: 200,
LatencyMS: 90,
Sync: true,
})
if err != nil {
t.Fatalf("AppendRouteDecisionLog() error = %v", err)
}
if decision.SelectedRouteID != "asxs" {
t.Fatalf("AppendRouteDecisionLog() = %+v, want selected route asxs", decision)
}
failover, err := actions.AppendRouteFailoverEvent(ctx, AppendRouteFailoverEventRequest{
RequestID: "req-2",
LogicalGroupID: "gpt-shared",
PublicModel: "gpt-5.4",
FromRouteID: "asxs",
ToRouteID: "codex2api",
Reason: "timeout",
FailureCount: 2,
Sync: true,
})
if err != nil {
t.Fatalf("AppendRouteFailoverEvent() error = %v", err)
}
if failover.ToRouteID != "codex2api" {
t.Fatalf("AppendRouteFailoverEvent() = %+v, want to_route_id codex2api", failover)
}
sticky, err := actions.AppendRouteStickyAudit(ctx, AppendRouteStickyAuditRequest{
StickyKey: "sticky-1",
StickyKeyType: "conversation",
LogicalGroupID: "gpt-shared",
PublicModel: "gpt-5.4",
RouteID: "asxs",
Action: "bind",
ExpiresAt: "2026-05-28T18:00:00Z",
Sync: true,
})
if err != nil {
t.Fatalf("AppendRouteStickyAudit() error = %v", err)
}
if sticky.Action != "bind" {
t.Fatalf("AppendRouteStickyAudit() = %+v, want action bind", sticky)
}
decisions, err := actions.ListRouteDecisionLogs(ctx, ListRouteDecisionLogsRequest{
RequestID: "req-1",
Limit: 10,
})
if err != nil {
t.Fatalf("ListRouteDecisionLogs() error = %v", err)
}
if len(decisions) != 1 || decisions[0].StickyKey != "sticky-1" {
t.Fatalf("ListRouteDecisionLogs() = %+v, want sticky-1", decisions)
}
failovers, err := actions.ListRouteFailoverEvents(ctx, ListRouteFailoverEventsRequest{
RequestID: "req-2",
Limit: 10,
})
if err != nil {
t.Fatalf("ListRouteFailoverEvents() error = %v", err)
}
if len(failovers) != 1 || failovers[0].FailureCount != 2 {
t.Fatalf("ListRouteFailoverEvents() = %+v, want failure_count 2", failovers)
}
stickyAudits, err := actions.ListRouteStickyAudit(ctx, ListRouteStickyAuditRequest{
StickyKey: "sticky-1",
Limit: 10,
})
if err != nil {
t.Fatalf("ListRouteStickyAudit() error = %v", err)
}
if len(stickyAudits) != 1 || stickyAudits[0].RouteID != "asxs" {
t.Fatalf("ListRouteStickyAudit() = %+v, want route asxs", stickyAudits)
}
}