fix(api): wire batch import create-run entry pipeline
This commit is contained in:
@@ -2,8 +2,14 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sub2api-cn-relay-manager/internal/store/sqlite"
|
||||
)
|
||||
|
||||
func TestBatchImportHTTP(t *testing.T) {
|
||||
@@ -126,4 +132,223 @@ func TestBatchImportHTTP(t *testing.T) {
|
||||
assertJSONContains(t, res.Body().Bytes(), "error.code", "invalid_request")
|
||||
assertJSONContains(t, res.Body().Bytes(), "error.message", "host_id is required")
|
||||
})
|
||||
|
||||
t.Run("create run action wires real batch pipeline", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := httptest.NewServer(newBatchImportActionStubServer(t))
|
||||
defer server.Close()
|
||||
|
||||
dsn := fmt.Sprintf("file:%s?_busy_timeout=5000&_pragma=foreign_keys(0)", filepath.ToSlash(filepath.Join(t.TempDir(), "state.db")))
|
||||
store, err := sqlite.Open(context.Background(), dsn)
|
||||
if err != nil {
|
||||
t.Fatalf("sqlite.Open() error = %v", err)
|
||||
}
|
||||
defer closeAppTestStore(t, store)
|
||||
|
||||
if _, err := store.Hosts().Create(context.Background(), sqlite.Host{
|
||||
HostID: "host-1",
|
||||
BaseURL: server.URL,
|
||||
HostVersion: "0.1.126",
|
||||
CapabilityProbeJSON: "{}",
|
||||
AuthType: "apikey",
|
||||
AuthToken: "host-token",
|
||||
}); err != nil {
|
||||
t.Fatalf("Hosts().Create() error = %v", err)
|
||||
}
|
||||
|
||||
action := buildCreateBatchImportRunAction(dsn)
|
||||
result, err := action(context.Background(), CreateBatchImportRunRequest{
|
||||
HostID: "host-1",
|
||||
Mode: "strict",
|
||||
AccessMode: "self_service",
|
||||
ConfirmWaitTimeoutSec: 1,
|
||||
ProbeAPIKey: "gateway-key",
|
||||
Entries: []BatchImportEntryRequest{
|
||||
{
|
||||
BaseURL: server.URL,
|
||||
APIKey: "entry-key",
|
||||
RequestedModels: []string{"kimi-k2.6"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("buildCreateBatchImportRunAction() error = %v", err)
|
||||
}
|
||||
if result.State != string("completed") {
|
||||
t.Fatalf("result.State = %q, want completed", result.State)
|
||||
}
|
||||
if result.ActiveItems != 1 {
|
||||
t.Fatalf("result.ActiveItems = %d, want 1", result.ActiveItems)
|
||||
}
|
||||
|
||||
run, err := store.ImportRuns().GetByRunID(context.Background(), result.RunID)
|
||||
if err != nil {
|
||||
t.Fatalf("ImportRuns().GetByRunID() error = %v", err)
|
||||
}
|
||||
if run.State != "completed" {
|
||||
t.Fatalf("run.State = %q, want completed", run.State)
|
||||
}
|
||||
items, err := store.ImportRunItems().ListByRunID(context.Background(), result.RunID)
|
||||
if err != nil {
|
||||
t.Fatalf("ImportRunItems().ListByRunID() error = %v", err)
|
||||
}
|
||||
if len(items) != 1 {
|
||||
t.Fatalf("len(items) = %d, want 1", len(items))
|
||||
}
|
||||
if items[0].CurrentStage != "done" || items[0].AccessStatus != "active" {
|
||||
t.Fatalf("item = %+v, want current_stage=done and access_status=active", items[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func newBatchImportActionStubServer(t *testing.T) http.Handler {
|
||||
t.Helper()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/api/v1/admin/system/version", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"version": "0.1.126"}})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/groups", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{}})
|
||||
case http.MethodPost:
|
||||
writeJSON(w, http.StatusCreated, map[string]any{"data": map[string]any{"id": "group_1", "name": "batch-import-group"}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/channels", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{}})
|
||||
case http.MethodPost:
|
||||
writeJSON(w, http.StatusCreated, map[string]any{"data": map[string]any{"id": "channel_1", "name": "batch-import-channel"}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/payment/plans", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{}})
|
||||
case http.MethodPost:
|
||||
writeJSON(w, http.StatusCreated, map[string]any{"data": map[string]any{"id": "plan_1", "name": "batch-import-plan"}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/accounts", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"items": []map[string]any{}, "pages": 1}})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/accounts/batch", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{{"id": "account_1", "name": "batch-import-01"}}})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/accounts/__probe__/test", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "probe only"})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/accounts/__probe__/models", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "probe only"})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/accounts/account_1/test", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("event: result\n"))
|
||||
_, _ = w.Write([]byte("data: {\"status\":\"passed\",\"message\":\"smoke passed\",\"ok\":true}\n\n"))
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/accounts/account_1/models", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"items": []map[string]any{{"id": "kimi-k2.6", "display_name": "Kimi K2.6", "type": "chat"}}}})
|
||||
})
|
||||
mux.HandleFunc("/api/v1/admin/subscriptions/assign", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !requireBatchImportActionAdminToken(t, w, r) {
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
writeJSON(w, http.StatusBadRequest, map[string]any{"error": "probe only"})
|
||||
case http.MethodPost:
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": map[string]any{"id": "subscription_1"}})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/v1/models", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch strings.TrimSpace(r.Header.Get("Authorization")) {
|
||||
case "Bearer entry-key", "Bearer gateway-key":
|
||||
writeJSON(w, http.StatusOK, map[string]any{"data": []map[string]any{{"id": "kimi-k2.6"}}})
|
||||
default:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
|
||||
}
|
||||
})
|
||||
mux.HandleFunc("/v1/responses", func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.TrimSpace(r.Header.Get("Authorization")) != "Bearer entry-key" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
_, _ = w.Write([]byte(`{"error":"responses unsupported"}`))
|
||||
})
|
||||
mux.HandleFunc("/v1/chat/completions", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch strings.TrimSpace(r.Header.Get("Authorization")) {
|
||||
case "Bearer entry-key", "Bearer gateway-key":
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"id": "chatcmpl_batch_import",
|
||||
"choices": []map[string]any{{
|
||||
"index": 0,
|
||||
"message": map[string]any{
|
||||
"role": "assistant",
|
||||
"content": "pong",
|
||||
},
|
||||
}},
|
||||
})
|
||||
default:
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
|
||||
}
|
||||
})
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func requireBatchImportActionAdminToken(t *testing.T, w http.ResponseWriter, r *http.Request) bool {
|
||||
t.Helper()
|
||||
if strings.TrimSpace(r.Header.Get("x-api-key")) != "host-token" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user