1084 lines
42 KiB
Go
1084 lines
42 KiB
Go
package provision
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sub2api-cn-relay-manager/internal/host/sub2api"
|
|
"sub2api-cn-relay-manager/internal/pack"
|
|
)
|
|
|
|
func TestImportServiceImportSubscriptionFlow(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1"}, {ID: "account_2"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "passed"},
|
|
"account_2": {OK: true, Status: "passed"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
"account_2": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
}
|
|
|
|
svc := NewImportService(host)
|
|
report, err := svc.Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{
|
|
Mode: AccessModeSubscription,
|
|
ProbeAPIKey: "user-key",
|
|
Subscriptions: []SubscriptionTarget{{UserID: "user_1", DurationDays: 30}},
|
|
},
|
|
Keys: []string{" key-1 ", "key-2", "key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSubscriptionReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSubscriptionReady)
|
|
}
|
|
if !reflect.DeepEqual(report.AcceptedKeys, []string{"key-1", "key-2"}) {
|
|
t.Fatalf("AcceptedKeys = %#v, want deduped normalized keys", report.AcceptedKeys)
|
|
}
|
|
if len(host.assignedSubscriptions) != 1 {
|
|
t.Fatalf("assigned subscriptions = %d, want 1", len(host.assignedSubscriptions))
|
|
}
|
|
if host.createGroupReq.SubscriptionType != "subscription" {
|
|
t.Fatalf("CreateGroup subscription_type = %q, want %q", host.createGroupReq.SubscriptionType, "subscription")
|
|
}
|
|
if host.createGroupReq.Platform != "openai" {
|
|
t.Fatalf("CreateGroup platform = %q, want %q", host.createGroupReq.Platform, "openai")
|
|
}
|
|
if host.gatewayProbe.ExpectedModel != "deepseek-chat" {
|
|
t.Fatalf("gateway probe model = %q, want %q", host.gatewayProbe.ExpectedModel, "deepseek-chat")
|
|
}
|
|
if host.testedModels["account_1"] != "deepseek-chat" || host.testedModels["account_2"] != "deepseek-chat" {
|
|
t.Fatalf("testedModels = %#v, want deepseek-chat for all created accounts", host.testedModels)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceStrictModeFailsWhenAnyAccountProbeFails(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1"}, {ID: "account_2"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "passed"},
|
|
"account_2": {OK: false, Status: "failed", Message: "bad key"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
"account_2": {{ID: "deepseek-chat"}},
|
|
},
|
|
}
|
|
|
|
svc := NewImportService(host)
|
|
report, err := svc.Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModeStrict,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1", "key-2"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("Import() error = nil, want strict mode failure")
|
|
}
|
|
if report.BatchStatus != BatchStatusFailed {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusFailed)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusFailed {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusFailed)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceRejectsUnknownMode(t *testing.T) {
|
|
svc := NewImportService(&fakeHostAdapter{})
|
|
_, err := svc.Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: "unknown",
|
|
Access: AccessRequest{Mode: AccessModeSelfService},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("Import() error = nil, want mode validation error")
|
|
}
|
|
}
|
|
|
|
func TestImportServiceMarksBrokenWhenCompletionSmokeFails(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "passed"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
completionResult: sub2api.GatewayCompletionResult{OK: false, StatusCode: 502, ContentType: "application/json", BodyPreview: `{"error":"upstream_error"}`},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusPartial {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusPartial)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusDegraded {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusDegraded)
|
|
}
|
|
if report.AccessStatus != AccessStatusBroken {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusBroken)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceTreatsResponsesOnlyProbeFailureAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "minimax-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: "账号本身可正常使用,但当前测试接口仅支持 Responses API 路径。请直接通过实际 API 调用验证。",
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"deepseek-chat"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
CompletionType: "text/event-stream",
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSelfServiceReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
|
|
}
|
|
if len(report.Accounts) != 1 {
|
|
t.Fatalf("Accounts len = %d, want 1", len(report.Accounts))
|
|
}
|
|
if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
|
|
t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceTreatsTransientProbeFailureAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: "API returned 429: {\"error\":{\"message\":\"Rate limited (429); user=1997/2000 model=49/50; daily_exhausted=False\",\"type\":\"rate_limit_error\",\"code\":\"rate_limit_exceeded\"}}",
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"deepseek-chat"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
CompletionType: "application/json",
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSelfServiceReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
|
|
}
|
|
if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
|
|
t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceTreatsForbiddenProbeRaceAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: "API returned 403: Forbidden",
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"deepseek-chat"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
CompletionType: "application/json",
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSelfServiceReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
|
|
}
|
|
if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
|
|
t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceKeepsProviderActiveWhenGatewayReadyDespiteSingleAccountProbeFailure(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "minimax-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: `API returned 401: {"code":"INVALID_API_KEY","message":"Invalid API key"}`,
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "MiniMax-M2.7-highspeed"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"MiniMax-M2.5-highspeed", "MiniMax-M2.7-highspeed"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
CompletionType: "text/event-stream",
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifestWithSmokeModel("MiniMax-M2.7-highspeed"),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{
|
|
Mode: AccessModeSubscription,
|
|
ProbeAPIKey: "user-key",
|
|
Subscriptions: []SubscriptionTarget{{UserID: "user_1", DurationDays: 30}},
|
|
},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusPartial {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusPartial)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSubscriptionReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSubscriptionReady)
|
|
}
|
|
if got := report.Accounts[0].ValidationStatus(); got != AccountStatusFailed {
|
|
t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusFailed)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceTreatsBare404ProbeAsAdvisoryWhenGatewaySucceeds(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: "API returned 404:",
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"deepseek-chat"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
CompletionType: "application/json",
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSelfServiceReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
|
|
}
|
|
if got := report.Accounts[0].ValidationStatus(); got != AccountStatusWarning {
|
|
t.Fatalf("ValidationStatus = %q, want %q", got, AccountStatusWarning)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceRetriesTransientGatewayCompletionFailure(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: "API returned 403: Forbidden",
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"deepseek-chat"},
|
|
},
|
|
completionResults: []sub2api.GatewayCompletionResult{
|
|
{OK: false, StatusCode: 503, ContentType: "application/json", BodyPreview: `{"error":{"message":"Service temporarily unavailable","type":"api_error"}}`},
|
|
{OK: true, StatusCode: 200, ContentType: "application/json"},
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if report.ProviderStatus != ProviderStatusActive {
|
|
t.Fatalf("ProviderStatus = %q, want %q", report.ProviderStatus, ProviderStatusActive)
|
|
}
|
|
if report.AccessStatus != AccessStatusSelfServiceReady {
|
|
t.Fatalf("AccessStatus = %q, want %q", report.AccessStatus, AccessStatusSelfServiceReady)
|
|
}
|
|
if !report.Gateway.CompletionOK || report.Gateway.CompletionStatus != 200 {
|
|
t.Fatalf("Gateway completion = %+v, want retried success", report.Gateway)
|
|
}
|
|
if host.completionCalls != 2 {
|
|
t.Fatalf("completion calls = %d, want 2", host.completionCalls)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceRepairsOpenAIResponsesCapabilityMismatchAfterInstall(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {
|
|
OK: false,
|
|
Status: "failed",
|
|
Message: "API returned 403: Forbidden",
|
|
},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"deepseek-chat"},
|
|
},
|
|
completionResults: []sub2api.GatewayCompletionResult{
|
|
{OK: false, StatusCode: 502, ContentType: "application/json", BodyPreview: `{"error":{"message":"Upstream service temporarily unavailable","type":"upstream_error"}}`},
|
|
},
|
|
completionAfterRepair: &sub2api.GatewayCompletionResult{OK: true, StatusCode: 200, ContentType: "application/json"},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if !report.Gateway.CompletionOK || report.Gateway.CompletionStatus != 200 {
|
|
t.Fatalf("Gateway completion = %+v, want repaired success", report.Gateway)
|
|
}
|
|
if host.disableResponsesCalls != 1 {
|
|
t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
|
|
}
|
|
if len(host.disabledResponsesAccountIDs) != 1 || host.disabledResponsesAccountIDs[0] != "account_1" {
|
|
t.Fatalf("disabled responses account ids = %v, want [account_1]", host.disabledResponsesAccountIDs)
|
|
}
|
|
if host.clearTempUnschedulableCalls != 1 {
|
|
t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
|
|
}
|
|
if len(host.clearedTempUnschedulableAccountIDs) != 1 || host.clearedTempUnschedulableAccountIDs[0] != "account_1" {
|
|
t.Fatalf("cleared temp unschedulable account ids = %v, want [account_1]", host.clearedTempUnschedulableAccountIDs)
|
|
}
|
|
}
|
|
|
|
func TestImportServicePreemptivelyDisablesResponsesForChatOnlyProvider(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "passed"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "kimi-k2.6"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"kimi-k2.6"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleChatOnlyProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
if host.disableResponsesCalls != 1 {
|
|
t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
|
|
}
|
|
if len(host.disabledResponsesAccountIDs) != 1 || host.disabledResponsesAccountIDs[0] != "account_1" {
|
|
t.Fatalf("disabled responses account ids = %v, want [account_1]", host.disabledResponsesAccountIDs)
|
|
}
|
|
if host.clearTempUnschedulableCalls != 1 {
|
|
t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
|
|
}
|
|
if got := host.callSequence; len(got) == 0 || got[0] != "disable_responses" {
|
|
t.Fatalf("callSequence = %v, want preemptive capability repair before gateway validation", got)
|
|
}
|
|
}
|
|
|
|
func TestImportServiceStrictModeRollsBackCreatedResources(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1"}, {ID: "account_2"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "passed"},
|
|
"account_2": {OK: false, Status: "failed", Message: "bad key"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
"account_2": {{ID: "deepseek-chat"}},
|
|
},
|
|
}
|
|
|
|
svc := NewImportService(host)
|
|
_, err := svc.Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModeStrict,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1", "key-2"},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("Import() error = nil, want strict mode failure")
|
|
}
|
|
|
|
want := []string{"account:account_2", "account:account_1", "channel:channel_1", "group:group_1"}
|
|
if !reflect.DeepEqual(host.deletedResources, want) {
|
|
t.Fatalf("deleted resources = %#v, want %#v", host.deletedResources, want)
|
|
}
|
|
}
|
|
|
|
func TestImportReusesExistingGroup(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "ready"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
managedSnapshot: sub2api.ManagedResourceSnapshot{
|
|
Groups: []sub2api.NamedResource{{ID: "group_existing", Name: "DeepSeek 默认分组-self-service"}},
|
|
},
|
|
}
|
|
|
|
svc := NewImportService(host)
|
|
report, err := svc.Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.Group.ID != "group_existing" {
|
|
t.Fatalf("Group.ID = %q, want reused group_existing", report.Group.ID)
|
|
}
|
|
if host.createGroupCalls != 0 {
|
|
t.Fatalf("CreateGroup() calls = %d, want 0 when group already exists", host.createGroupCalls)
|
|
}
|
|
if host.createChannelCalls != 1 {
|
|
t.Fatalf("CreateChannel() calls = %d, want 1", host.createChannelCalls)
|
|
}
|
|
}
|
|
|
|
func TestImportCreatesChannelWithManifestModelMapping(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "ready"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
}
|
|
|
|
_, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if host.createChannelReq.Name != "DeepSeek 默认渠道-self-service" {
|
|
t.Fatalf("CreateChannel().Name = %q, want DeepSeek 默认渠道-self-service", host.createChannelReq.Name)
|
|
}
|
|
if len(host.createChannelReq.GroupIDs) != 1 || host.createChannelReq.GroupIDs[0] != "group_1" {
|
|
t.Fatalf("CreateChannel().GroupIDs = %v, want [group_1]", host.createChannelReq.GroupIDs)
|
|
}
|
|
if got := host.createChannelReq.ModelMapping["deepseek-chat"]; got != "deepseek-chat" {
|
|
t.Fatalf("CreateChannel().ModelMapping = %+v, want deepseek-chat passthrough", host.createChannelReq.ModelMapping)
|
|
}
|
|
if !host.createChannelReq.RestrictModels {
|
|
t.Fatal("CreateChannel().RestrictModels = false, want true")
|
|
}
|
|
if host.createChannelReq.BillingModelSource != "channel_mapped" {
|
|
t.Fatalf("CreateChannel().BillingModelSource = %q, want channel_mapped", host.createChannelReq.BillingModelSource)
|
|
}
|
|
if len(host.createChannelReq.ModelPricing) != 1 {
|
|
t.Fatalf("CreateChannel().ModelPricing len = %d, want 1", len(host.createChannelReq.ModelPricing))
|
|
}
|
|
if len(host.createChannelReq.ModelPricing[0].Models) != 2 {
|
|
t.Fatalf("CreateChannel().ModelPricing[0].Models = %v, want default model coverage", host.createChannelReq.ModelPricing[0].Models)
|
|
}
|
|
if host.createChannelReq.ModelPricing[0].BillingMode != "token" {
|
|
t.Fatalf("CreateChannel().ModelPricing[0].BillingMode = %q, want token", host.createChannelReq.ModelPricing[0].BillingMode)
|
|
}
|
|
if len(host.batchCreateReq.Accounts) != 1 {
|
|
t.Fatalf("BatchCreateAccounts().Accounts len = %d, want 1", len(host.batchCreateReq.Accounts))
|
|
}
|
|
credentials := host.batchCreateReq.Accounts[0].Credentials
|
|
switch rawMapping := credentials["model_mapping"].(type) {
|
|
case map[string]string:
|
|
if got := rawMapping["deepseek-chat"]; got != "deepseek-chat" {
|
|
t.Fatalf("BatchCreateAccounts().Credentials.model_mapping = %+v, want deepseek-chat passthrough", rawMapping)
|
|
}
|
|
case map[string]any:
|
|
if got, _ := rawMapping["deepseek-chat"].(string); got != "deepseek-chat" {
|
|
t.Fatalf("BatchCreateAccounts().Credentials.model_mapping = %+v, want deepseek-chat passthrough", rawMapping)
|
|
}
|
|
default:
|
|
t.Fatalf("BatchCreateAccounts().Credentials = %+v, want model_mapping map", credentials)
|
|
}
|
|
}
|
|
|
|
func sampleProviderManifest() pack.ProviderManifest {
|
|
return pack.ProviderManifest{
|
|
ProviderID: "deepseek",
|
|
DisplayName: "DeepSeek OpenAI Compatible",
|
|
BaseURL: "https://api.deepseek.com",
|
|
Platform: "openai",
|
|
AccountType: "apikey",
|
|
DefaultModels: []string{"deepseek-chat", "deepseek-reasoner"},
|
|
SmokeTestModel: "deepseek-chat",
|
|
GroupTemplate: pack.GroupTemplate{Name: "DeepSeek 默认分组", RateMultiplier: 1},
|
|
ChannelTemplate: pack.ChannelTemplate{Name: "DeepSeek 默认渠道", ModelMapping: map[string]string{"deepseek-chat": "deepseek-chat"}},
|
|
PlanTemplate: pack.PlanTemplate{Name: "DeepSeek 默认套餐", Price: 19.9, ValidityDays: 30, ValidityUnit: "day"},
|
|
}
|
|
}
|
|
|
|
func sampleProviderManifestWithSmokeModel(smokeModel string) pack.ProviderManifest {
|
|
provider := sampleProviderManifest()
|
|
provider.ProviderID = "minimax-53hk"
|
|
provider.DisplayName = "MiniMax 53hk"
|
|
provider.BaseURL = "https://api.minimax.chat/v1"
|
|
provider.DefaultModels = []string{"MiniMax-M2.5-highspeed", strings.TrimSpace(smokeModel)}
|
|
provider.SmokeTestModel = strings.TrimSpace(smokeModel)
|
|
provider.ChannelTemplate = pack.ChannelTemplate{
|
|
Name: "MiniMax 53hk 默认渠道",
|
|
ModelMapping: map[string]string{
|
|
"MiniMax-M2.5-highspeed": "MiniMax-M2.5-highspeed",
|
|
strings.TrimSpace(smokeModel): strings.TrimSpace(smokeModel),
|
|
},
|
|
}
|
|
provider.GroupTemplate = pack.GroupTemplate{Name: "MiniMax 53hk 默认分组", RateMultiplier: 1}
|
|
provider.PlanTemplate = pack.PlanTemplate{Name: "MiniMax 53hk 默认套餐", Price: 19.9, ValidityDays: 30, ValidityUnit: "day"}
|
|
return provider
|
|
}
|
|
|
|
func sampleChatOnlyProviderManifest() pack.ProviderManifest {
|
|
provider := sampleProviderManifest()
|
|
provider.ProviderID = "kimi-a7m"
|
|
provider.DisplayName = "Kimi A7M OpenAI Compatible"
|
|
provider.BaseURL = "https://kimi.a7m.com.cn/v1"
|
|
provider.DefaultModels = []string{"kimi-k2.6"}
|
|
provider.SmokeTestModel = "kimi-k2.6"
|
|
provider.ChannelTemplate = pack.ChannelTemplate{Name: "Kimi A7M 默认渠道", ModelMapping: map[string]string{"kimi-k2.6": "kimi-k2.6"}}
|
|
provider.GroupTemplate = pack.GroupTemplate{Name: "Kimi A7M 默认分组", RateMultiplier: 1}
|
|
provider.PlanTemplate = pack.PlanTemplate{Name: "Kimi A7M 默认套餐", Price: 19.9, ValidityDays: 30, ValidityUnit: "day"}
|
|
provider.ForceDisableOpenAIResponsesAPI = true
|
|
return provider
|
|
}
|
|
|
|
func TestImportServicePreemptiveResponsesRepairHappensBeforeAccountProbe(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "kimi-a7m-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "passed"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "kimi-k2.6"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{
|
|
OK: true,
|
|
StatusCode: 200,
|
|
HasExpectedModel: true,
|
|
Models: []string{"kimi-k2.6"},
|
|
CompletionOK: true,
|
|
CompletionStatus: 200,
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleChatOnlyProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusSucceeded {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusSucceeded)
|
|
}
|
|
wantSequence := []string{"disable_responses", "clear_temp_unschedulable", "testAccount:account_1", "gateway", "completion"}
|
|
if !reflect.DeepEqual(host.callSequence, wantSequence) {
|
|
t.Fatalf("callSequence = %v, want %v", host.callSequence, wantSequence)
|
|
}
|
|
if host.disableResponsesCalls != 1 {
|
|
t.Fatalf("disable responses calls = %d, want 1", host.disableResponsesCalls)
|
|
}
|
|
if len(host.disabledResponsesAccountIDs) != 1 || host.disabledResponsesAccountIDs[0] != "account_1" {
|
|
t.Fatalf("disabled responses account ids = %v, want [account_1]", host.disabledResponsesAccountIDs)
|
|
}
|
|
if host.clearTempUnschedulableCalls != 1 {
|
|
t.Fatalf("clear temp unschedulable calls = %d, want 1", host.clearTempUnschedulableCalls)
|
|
}
|
|
}
|
|
|
|
func TestImportReconcilesExistingChannelConfiguration(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_1": {OK: true, Status: "ready"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
managedSnapshot: sub2api.ManagedResourceSnapshot{
|
|
Groups: []sub2api.NamedResource{{ID: "group_existing", Name: "DeepSeek 默认分组-self-service"}},
|
|
Channels: []sub2api.NamedResource{{ID: "channel_existing", Name: "DeepSeek 默认渠道-self-service"}},
|
|
},
|
|
}
|
|
|
|
_, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if host.createChannelCalls != 0 {
|
|
t.Fatalf("CreateChannel() calls = %d, want 0 when channel already exists", host.createChannelCalls)
|
|
}
|
|
if host.updateChannelCalls != 1 {
|
|
t.Fatalf("UpdateChannel() calls = %d, want 1", host.updateChannelCalls)
|
|
}
|
|
if host.updateChannelID != "channel_existing" {
|
|
t.Fatalf("UpdateChannel() id = %q, want channel_existing", host.updateChannelID)
|
|
}
|
|
if len(host.updateChannelReq.ModelPricing) != 1 {
|
|
t.Fatalf("UpdateChannel().ModelPricing len = %d, want 1", len(host.updateChannelReq.ModelPricing))
|
|
}
|
|
}
|
|
|
|
func TestImportDeletesExistingProviderAccountsBeforeGatewayClosure(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_new_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_new_1": {OK: true, Status: "passed"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_new_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
managedSnapshot: sub2api.ManagedResourceSnapshot{
|
|
Accounts: []sub2api.NamedResource{{ID: "account_old_1", Name: "deepseek-01"}, {ID: "account_old_2", Name: "deepseek-02"}},
|
|
},
|
|
}
|
|
|
|
_, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if host.listManagedReq.AccountNamePrefix != "deepseek-" {
|
|
t.Fatalf("AccountNamePrefix = %q, want %q", host.listManagedReq.AccountNamePrefix, "deepseek-")
|
|
}
|
|
wantDeleted := []string{"account:account_old_2", "account:account_old_1"}
|
|
if !reflect.DeepEqual(host.deletedResources, wantDeleted) {
|
|
t.Fatalf("deleted resources = %#v, want %#v", host.deletedResources, wantDeleted)
|
|
}
|
|
if !reflect.DeepEqual(host.callSequence, []string{"testAccount:account_new_1", "deleteAccount:account_old_2", "deleteAccount:account_old_1", "gateway", "completion"}) {
|
|
t.Fatalf("call sequence = %#v, want stale-account cleanup before gateway probe", host.callSequence)
|
|
}
|
|
}
|
|
|
|
func TestImportKeepsExistingAccountsWhenReplacementValidationFails(t *testing.T) {
|
|
host := &fakeHostAdapter{
|
|
batchAccounts: []sub2api.AccountRef{{ID: "account_new_1", Name: "deepseek-01"}},
|
|
testResults: map[string]sub2api.ProbeResult{
|
|
"account_new_1": {OK: false, Status: "failed", Message: "bad key"},
|
|
},
|
|
models: map[string][]sub2api.AccountModel{
|
|
"account_new_1": {{ID: "deepseek-chat"}},
|
|
},
|
|
gatewayResult: sub2api.GatewayAccessResult{OK: true, StatusCode: 200, HasExpectedModel: true, Models: []string{"deepseek-chat"}},
|
|
managedSnapshot: sub2api.ManagedResourceSnapshot{
|
|
Accounts: []sub2api.NamedResource{{ID: "account_old_1", Name: "deepseek-01"}},
|
|
},
|
|
}
|
|
|
|
report, err := NewImportService(host).Import(context.Background(), ImportRequest{
|
|
Provider: sampleProviderManifest(),
|
|
Mode: ImportModePartial,
|
|
Access: AccessRequest{Mode: AccessModeSelfService, ProbeAPIKey: "user-key"},
|
|
Keys: []string{"key-1"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Import() error = %v", err)
|
|
}
|
|
if report.BatchStatus != BatchStatusPartial {
|
|
t.Fatalf("BatchStatus = %q, want %q", report.BatchStatus, BatchStatusPartial)
|
|
}
|
|
if len(host.deletedResources) != 0 {
|
|
t.Fatalf("deleted resources = %#v, want no stale-account cleanup when replacement validation fails", host.deletedResources)
|
|
}
|
|
}
|
|
|
|
type fakeHostAdapter struct {
|
|
batchAccounts []sub2api.AccountRef
|
|
batchCreateReq sub2api.BatchCreateAccountsRequest
|
|
testResults map[string]sub2api.ProbeResult
|
|
models map[string][]sub2api.AccountModel
|
|
gatewayResult sub2api.GatewayAccessResult
|
|
batchCreateErr error
|
|
assignErr error
|
|
gatewayErr error
|
|
hostVersion string
|
|
assignedSubscriptions []sub2api.AssignSubscriptionRequest
|
|
gatewayProbe sub2api.GatewayAccessCheckRequest
|
|
completionProbe sub2api.GatewayCompletionCheckRequest
|
|
deletedResources []string
|
|
managedSnapshot sub2api.ManagedResourceSnapshot
|
|
listManagedReq sub2api.ListManagedResourcesRequest
|
|
createGroupCalls int
|
|
createChannelCalls int
|
|
updateChannelCalls int
|
|
createPlanCalls int
|
|
createGroupReq sub2api.CreateGroupRequest
|
|
createChannelReq sub2api.CreateChannelRequest
|
|
updateChannelID string
|
|
updateChannelReq sub2api.CreateChannelRequest
|
|
callSequence []string
|
|
completionCalls int
|
|
completionResults []sub2api.GatewayCompletionResult
|
|
completionResult sub2api.GatewayCompletionResult
|
|
completionAfterRepair *sub2api.GatewayCompletionResult
|
|
completionErr error
|
|
testedModels map[string]string
|
|
disableResponsesCalls int
|
|
disabledResponsesAccountIDs []string
|
|
clearTempUnschedulableCalls int
|
|
clearedTempUnschedulableAccountIDs []string
|
|
deleteErrors map[string]error
|
|
}
|
|
|
|
func (f *fakeHostAdapter) GetHostVersion(context.Context) (string, error) {
|
|
if strings.TrimSpace(f.hostVersion) == "" {
|
|
return "0.1.126", nil
|
|
}
|
|
return f.hostVersion, nil
|
|
}
|
|
func (f *fakeHostAdapter) ProbeCapabilities(context.Context) (sub2api.HostCapabilities, error) {
|
|
return sub2api.HostCapabilities{}, nil
|
|
}
|
|
func (f *fakeHostAdapter) CreateGroup(_ context.Context, req sub2api.CreateGroupRequest) (sub2api.GroupRef, error) {
|
|
f.createGroupCalls++
|
|
f.createGroupReq = req
|
|
return sub2api.GroupRef{ID: "group_1", Name: "g"}, nil
|
|
}
|
|
func (f *fakeHostAdapter) DeleteGroup(_ context.Context, groupID string) error {
|
|
if err := f.deleteErrors["group:"+groupID]; err != nil {
|
|
return err
|
|
}
|
|
f.deletedResources = append(f.deletedResources, "group:"+groupID)
|
|
return nil
|
|
}
|
|
func (f *fakeHostAdapter) CreateChannel(_ context.Context, req sub2api.CreateChannelRequest) (sub2api.ChannelRef, error) {
|
|
f.createChannelCalls++
|
|
f.createChannelReq = req
|
|
return sub2api.ChannelRef{ID: "channel_1", Name: "c"}, nil
|
|
}
|
|
func (f *fakeHostAdapter) UpdateChannel(_ context.Context, channelID string, req sub2api.CreateChannelRequest) error {
|
|
f.updateChannelCalls++
|
|
f.updateChannelID = channelID
|
|
f.updateChannelReq = req
|
|
return nil
|
|
}
|
|
func (f *fakeHostAdapter) DeleteChannel(_ context.Context, channelID string) error {
|
|
if err := f.deleteErrors["channel:"+channelID]; err != nil {
|
|
return err
|
|
}
|
|
f.deletedResources = append(f.deletedResources, "channel:"+channelID)
|
|
return nil
|
|
}
|
|
func (f *fakeHostAdapter) CreatePlan(context.Context, sub2api.CreatePlanRequest) (sub2api.PlanRef, error) {
|
|
f.createPlanCalls++
|
|
return sub2api.PlanRef{ID: "plan_1", Name: "p"}, nil
|
|
}
|
|
func (f *fakeHostAdapter) DeletePlan(_ context.Context, planID string) error {
|
|
if err := f.deleteErrors["plan:"+planID]; err != nil {
|
|
return err
|
|
}
|
|
f.deletedResources = append(f.deletedResources, "plan:"+planID)
|
|
return nil
|
|
}
|
|
func (f *fakeHostAdapter) CreateAccount(context.Context, sub2api.CreateAccountRequest) (sub2api.AccountRef, error) {
|
|
return sub2api.AccountRef{}, errors.New("unused")
|
|
}
|
|
func (f *fakeHostAdapter) BatchCreateAccounts(_ context.Context, req sub2api.BatchCreateAccountsRequest) ([]sub2api.AccountRef, error) {
|
|
f.batchCreateReq = req
|
|
if f.batchCreateErr != nil {
|
|
return nil, f.batchCreateErr
|
|
}
|
|
return f.batchAccounts, nil
|
|
}
|
|
func (f *fakeHostAdapter) DeleteAccount(_ context.Context, accountID string) error {
|
|
if err := f.deleteErrors["account:"+accountID]; err != nil {
|
|
return err
|
|
}
|
|
f.callSequence = append(f.callSequence, "deleteAccount:"+accountID)
|
|
f.deletedResources = append(f.deletedResources, "account:"+accountID)
|
|
return nil
|
|
}
|
|
func (f *fakeHostAdapter) TestAccount(_ context.Context, accountID, modelID string) (sub2api.ProbeResult, error) {
|
|
f.callSequence = append(f.callSequence, "testAccount:"+accountID)
|
|
if f.testedModels == nil {
|
|
f.testedModels = map[string]string{}
|
|
}
|
|
f.testedModels[accountID] = modelID
|
|
result, ok := f.testResults[accountID]
|
|
if !ok {
|
|
return sub2api.ProbeResult{}, fmt.Errorf("missing test result for %s", accountID)
|
|
}
|
|
return result, nil
|
|
}
|
|
func (f *fakeHostAdapter) GetAccountModels(_ context.Context, accountID string) ([]sub2api.AccountModel, error) {
|
|
models, ok := f.models[accountID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("missing models for %s", accountID)
|
|
}
|
|
return models, nil
|
|
}
|
|
func (f *fakeHostAdapter) EnsureSubscriptionAccess(_ context.Context, req sub2api.EnsureSubscriptionAccessRequest) (sub2api.SubscriptionAccessRef, error) {
|
|
return sub2api.SubscriptionAccessRef{UserID: req.UserSelector, APIKey: "managed-subscription-key"}, nil
|
|
}
|
|
func (f *fakeHostAdapter) AssignSubscription(_ context.Context, req sub2api.AssignSubscriptionRequest) (sub2api.SubscriptionRef, error) {
|
|
if f.assignErr != nil {
|
|
return sub2api.SubscriptionRef{}, f.assignErr
|
|
}
|
|
f.assignedSubscriptions = append(f.assignedSubscriptions, req)
|
|
return sub2api.SubscriptionRef{ID: "subscription_1"}, nil
|
|
}
|
|
func (f *fakeHostAdapter) CheckGatewayAccess(_ context.Context, req sub2api.GatewayAccessCheckRequest) (sub2api.GatewayAccessResult, error) {
|
|
f.callSequence = append(f.callSequence, "gateway")
|
|
f.gatewayProbe = req
|
|
if f.gatewayErr != nil {
|
|
return sub2api.GatewayAccessResult{}, f.gatewayErr
|
|
}
|
|
return f.gatewayResult, nil
|
|
}
|
|
func (f *fakeHostAdapter) CheckGatewayCompletion(_ context.Context, req sub2api.GatewayCompletionCheckRequest) (sub2api.GatewayCompletionResult, error) {
|
|
f.callSequence = append(f.callSequence, "completion")
|
|
f.completionProbe = req
|
|
f.completionCalls++
|
|
if f.completionErr != nil {
|
|
return sub2api.GatewayCompletionResult{}, f.completionErr
|
|
}
|
|
if f.disableResponsesCalls > 0 && f.completionAfterRepair != nil {
|
|
return *f.completionAfterRepair, nil
|
|
}
|
|
if len(f.completionResults) > 0 {
|
|
idx := f.completionCalls - 1
|
|
if idx >= len(f.completionResults) {
|
|
idx = len(f.completionResults) - 1
|
|
}
|
|
return f.completionResults[idx], nil
|
|
}
|
|
if f.completionResult.StatusCode == 0 && !f.completionResult.OK {
|
|
return sub2api.GatewayCompletionResult{OK: true, StatusCode: 200, ContentType: "application/json"}, nil
|
|
}
|
|
return f.completionResult, nil
|
|
}
|
|
func (f *fakeHostAdapter) DisableOpenAIResponsesAPI(_ context.Context, accountIDs []string) error {
|
|
f.callSequence = append(f.callSequence, "disable_responses")
|
|
f.disableResponsesCalls++
|
|
f.disabledResponsesAccountIDs = append([]string(nil), accountIDs...)
|
|
return nil
|
|
}
|
|
|
|
func (f *fakeHostAdapter) ClearTempUnschedulable(_ context.Context, accountIDs []string) error {
|
|
f.callSequence = append(f.callSequence, "clear_temp_unschedulable")
|
|
f.clearTempUnschedulableCalls++
|
|
f.clearedTempUnschedulableAccountIDs = append([]string(nil), accountIDs...)
|
|
return nil
|
|
}
|
|
func (f *fakeHostAdapter) ListManagedResources(_ context.Context, req sub2api.ListManagedResourcesRequest) (sub2api.ManagedResourceSnapshot, error) {
|
|
f.listManagedReq = req
|
|
return sub2api.ManagedResourceSnapshot{
|
|
Groups: filterNamedResourcesByExactName(f.managedSnapshot.Groups, req.GroupName),
|
|
Channels: filterNamedResourcesByExactName(f.managedSnapshot.Channels, req.ChannelName),
|
|
Plans: filterNamedResourcesByExactName(f.managedSnapshot.Plans, req.PlanName),
|
|
Accounts: filterNamedResourcesByPrefix(f.managedSnapshot.Accounts, req.AccountNamePrefix),
|
|
}, nil
|
|
}
|
|
|
|
func filterNamedResourcesByExactName(resources []sub2api.NamedResource, expected string) []sub2api.NamedResource {
|
|
expected = strings.TrimSpace(expected)
|
|
if expected == "" {
|
|
return nil
|
|
}
|
|
filtered := make([]sub2api.NamedResource, 0, len(resources))
|
|
for _, resource := range resources {
|
|
if strings.TrimSpace(resource.Name) == expected {
|
|
filtered = append(filtered, resource)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
func filterNamedResourcesByPrefix(resources []sub2api.NamedResource, prefix string) []sub2api.NamedResource {
|
|
prefix = strings.TrimSpace(prefix)
|
|
if prefix == "" {
|
|
return resources
|
|
}
|
|
filtered := make([]sub2api.NamedResource, 0, len(resources))
|
|
for _, resource := range resources {
|
|
if strings.HasPrefix(strings.TrimSpace(resource.Name), prefix) {
|
|
filtered = append(filtered, resource)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|