Files
sub2api-cn-relay-manager/internal/provision/runtime_import_service_test.go
2026-05-25 07:30:07 +08:00

740 lines
27 KiB
Go

package provision
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"strings"
"testing"
_ "modernc.org/sqlite"
"sub2api-cn-relay-manager/internal/host/sub2api"
"sub2api-cn-relay-manager/internal/pack"
"sub2api-cn-relay-manager/internal/store/sqlite"
"sub2api-cn-relay-manager/internal/testutil"
)
func TestRuntimeImportServicePersistsOperationalState(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
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 := NewRuntimeImportService(store, host)
result, err := svc.Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{" key-1 ", "key-2", "key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
if result.BatchID <= 0 {
t.Fatalf("BatchID = %d, want positive id", result.BatchID)
}
if result.Report.BatchStatus != BatchStatusSucceeded {
t.Fatalf("BatchStatus = %q, want %q", result.Report.BatchStatus, BatchStatusSucceeded)
}
if got := queryCount(t, store.SQLDB(), "hosts"); got != 1 {
t.Fatalf("hosts row count = %d, want 1", got)
}
if got := queryCount(t, store.SQLDB(), "packs"); got != 1 {
t.Fatalf("packs row count = %d, want 1", got)
}
if got := queryCount(t, store.SQLDB(), "providers"); got != 1 {
t.Fatalf("providers row count = %d, want 1", got)
}
if got := queryCount(t, store.SQLDB(), "import_batches"); got != 1 {
t.Fatalf("import_batches row count = %d, want 1", got)
}
if got := queryCount(t, store.SQLDB(), "import_batch_items"); got != 2 {
t.Fatalf("import_batch_items row count = %d, want 2", got)
}
if got := queryCount(t, store.SQLDB(), "managed_resources"); got != 4 {
t.Fatalf("managed_resources row count = %d, want 4", got)
}
if got := queryCount(t, store.SQLDB(), "probe_results"); got != 2 {
t.Fatalf("probe_results row count = %d, want 2", got)
}
if got := queryCount(t, store.SQLDB(), "access_closure_records"); got != 1 {
t.Fatalf("access_closure_records row count = %d, want 1", got)
}
var batchStatus string
var accessStatus string
if err := store.SQLDB().QueryRowContext(context.Background(), "SELECT batch_status, access_status FROM import_batches WHERE id = ?", result.BatchID).Scan(&batchStatus, &accessStatus); err != nil {
t.Fatalf("query import batch state: %v", err)
}
if batchStatus != BatchStatusSucceeded {
t.Fatalf("persisted batch_status = %q, want %q", batchStatus, BatchStatusSucceeded)
}
if accessStatus != AccessStatusSelfServiceReady {
t.Fatalf("persisted access_status = %q, want %q", accessStatus, AccessStatusSelfServiceReady)
}
var fingerprint string
var accountStatus string
if err := store.SQLDB().QueryRowContext(context.Background(), "SELECT key_fingerprint, account_status FROM import_batch_items ORDER BY id LIMIT 1").Scan(&fingerprint, &accountStatus); err != nil {
t.Fatalf("query import batch item: %v", err)
}
if fingerprint == "key-1" || fingerprint == "key-2" || len(fingerprint) < 10 {
t.Fatalf("key_fingerprint = %q, want hashed fingerprint instead of raw key", fingerprint)
}
if accountStatus != "passed" {
t.Fatalf("account_status = %q, want passed", accountStatus)
}
}
func TestRuntimeImportServicePersistsFailedBatchAfterStrictRollback(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
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 := NewRuntimeImportService(store, host)
result, err := svc.Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModeStrict,
Keys: []string{"key-1", "key-2"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err == nil {
t.Fatal("RuntimeImportService.Import() error = nil, want strict failure")
}
if result.BatchID <= 0 {
t.Fatalf("BatchID = %d, want positive id", result.BatchID)
}
var batchStatus string
var accessStatus string
if err := store.SQLDB().QueryRowContext(context.Background(), "SELECT batch_status, access_status FROM import_batches WHERE id = ?", result.BatchID).Scan(&batchStatus, &accessStatus); err != nil {
t.Fatalf("query failed import batch state: %v", err)
}
if batchStatus != BatchStatusFailed {
t.Fatalf("persisted batch_status = %q, want %q", batchStatus, BatchStatusFailed)
}
if accessStatus != AccessStatusBroken {
t.Fatalf("persisted access_status = %q, want %q", accessStatus, AccessStatusBroken)
}
if got := queryCount(t, store.SQLDB(), "managed_resources"); got != 0 {
t.Fatalf("managed_resources row count = %d, want 0 after strict rollback", got)
}
if got := queryCount(t, store.SQLDB(), "probe_results"); got != 2 {
t.Fatalf("probe_results row count = %d, want 2", got)
}
if got := queryCount(t, store.SQLDB(), "access_closure_records"); got != 1 {
t.Fatalf("access_closure_records row count = %d, want 1", got)
}
}
func TestRuntimeImportServicePersistsWarningAccountStatusForAdvisoryProbeFailure(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
host := &fakeHostAdapter{
batchAccounts: []sub2api.AccountRef{{ID: "account_1"}},
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,
},
}
svc := NewRuntimeImportService(store, host)
result, err := svc.Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
if result.Report.BatchStatus != BatchStatusSucceeded {
t.Fatalf("BatchStatus = %q, want %q", result.Report.BatchStatus, BatchStatusSucceeded)
}
var accountStatus string
var summary string
if err := store.SQLDB().QueryRowContext(context.Background(), "SELECT account_status, probe_summary_json FROM import_batch_items WHERE batch_id = ? ORDER BY id LIMIT 1", result.BatchID).Scan(&accountStatus, &summary); err != nil {
t.Fatalf("query import batch item: %v", err)
}
if accountStatus != AccountStatusWarning {
t.Fatalf("account_status = %q, want %q", accountStatus, AccountStatusWarning)
}
if !strings.Contains(summary, "\"probe_advisory\":true") {
t.Fatalf("probe_summary_json = %s, want probe_advisory=true", summary)
}
}
func TestRuntimeImportServicePersistsWarningAccountStatusForForbiddenProbeRace(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
host := &fakeHostAdapter{
batchAccounts: []sub2api.AccountRef{{ID: "account_1"}},
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,
},
}
svc := NewRuntimeImportService(store, host)
result, err := svc.Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
if result.Report.BatchStatus != BatchStatusSucceeded {
t.Fatalf("BatchStatus = %q, want %q", result.Report.BatchStatus, BatchStatusSucceeded)
}
var accountStatus string
var summary string
if err := store.SQLDB().QueryRowContext(context.Background(), "SELECT account_status, probe_summary_json FROM import_batch_items WHERE batch_id = ? ORDER BY id LIMIT 1", result.BatchID).Scan(&accountStatus, &summary); err != nil {
t.Fatalf("query import batch item: %v", err)
}
if accountStatus != AccountStatusWarning {
t.Fatalf("account_status = %q, want %q", accountStatus, AccountStatusWarning)
}
if !strings.Contains(summary, "\"probe_advisory\":true") {
t.Fatalf("probe_summary_json = %s, want probe_advisory=true", summary)
}
}
func TestRuntimeImportServicePersistsPartialManagedResourcesOnAccessFailure(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
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"}},
},
assignErr: fmt.Errorf("group is not a subscription type"),
}
svc := NewRuntimeImportService(store, host)
result, err := svc.Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSubscription,
ProbeAPIKey: "user-key",
Subscriptions: []SubscriptionTarget{{UserID: "1", DurationDays: 30}},
},
})
if err == nil {
t.Fatal("RuntimeImportService.Import() error = nil, want partial failure")
}
if result.BatchID <= 0 {
t.Fatalf("BatchID = %d, want positive id", result.BatchID)
}
if got := queryCount(t, store.SQLDB(), "managed_resources"); got != 4 {
t.Fatalf("managed_resources row count = %d, want 4 persisted resources on partial failure", got)
}
var batchStatus string
if err := store.SQLDB().QueryRowContext(context.Background(), "SELECT batch_status FROM import_batches WHERE id = ?", result.BatchID).Scan(&batchStatus); err != nil {
t.Fatalf("query import batch status: %v", err)
}
if batchStatus != BatchStatusPartial {
t.Fatalf("persisted batch_status = %q, want %q", batchStatus, BatchStatusPartial)
}
}
func TestRuntimeImportServicePersistsSelfServiceProbeKeyInAccessClosure(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
host := &fakeHostAdapter{
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
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"}, CompletionOK: true, CompletionStatus: 200},
}
result, err := NewRuntimeImportService(store, host).Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-probe-key",
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
closures, err := store.AccessClosures().GetByBatchID(context.Background(), result.BatchID)
if err != nil {
t.Fatalf("AccessClosures().GetByBatchID() error = %v", err)
}
if len(closures) != 1 {
t.Fatalf("access closures = %d, want 1", len(closures))
}
var payload map[string]any
if err := json.Unmarshal([]byte(closures[0].DetailsJSON), &payload); err != nil {
t.Fatalf("decode access closure details: %v", err)
}
if got, _ := payload["probe_api_key"].(string); got != "user-probe-key" {
t.Fatalf("probe_api_key = %q, want user-probe-key", got)
}
if got, _ := payload["requested_probe_api_key"].(string); got != "user-probe-key" {
t.Fatalf("requested_probe_api_key = %q, want user-probe-key", got)
}
if got, _ := payload["effective_probe_key_source"].(string); got != "requested_probe_api_key" {
t.Fatalf("effective_probe_key_source = %q, want requested_probe_api_key", got)
}
if got, _ := payload["effective_probe_key_fingerprint"].(string); got != fingerprintSecret("user-probe-key") {
t.Fatalf("effective_probe_key_fingerprint = %q, want hashed user-probe-key", got)
}
}
func TestRuntimeImportServicePersistsSubscriptionMetadataInAccessClosure(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
host := &fakeHostAdapter{
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}},
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"}, CompletionOK: true, CompletionStatus: 200},
}
result, err := NewRuntimeImportService(store, host).Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSubscription,
Subscriptions: []SubscriptionTarget{{UserID: "user-42", DurationDays: 30}},
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
closures, err := store.AccessClosures().GetByBatchID(context.Background(), result.BatchID)
if err != nil {
t.Fatalf("AccessClosures().GetByBatchID() error = %v", err)
}
if len(closures) != 1 {
t.Fatalf("access closures = %d, want 1", len(closures))
}
var payload map[string]any
if err := json.Unmarshal([]byte(closures[0].DetailsJSON), &payload); err != nil {
t.Fatalf("decode access closure details: %v", err)
}
users, _ := payload["subscription_users"].([]any)
if len(users) != 1 || users[0] != "user-42" {
t.Fatalf("subscription_users = %#v, want [user-42]", users)
}
if got, _ := payload["subscription_days"].(float64); int(got) != 30 {
t.Fatalf("subscription_days = %v, want 30", got)
}
if _, ok := payload["probe_api_key"]; ok {
t.Fatalf("probe_api_key should be omitted for subscription closure, got %#v", payload["probe_api_key"])
}
if got, _ := payload["effective_probe_key_source"].(string); got != "managed_subscription" {
t.Fatalf("effective_probe_key_source = %q, want managed_subscription", got)
}
if got, _ := payload["effective_probe_key_fingerprint"].(string); got != fingerprintSecret("managed-subscription-key") {
t.Fatalf("effective_probe_key_fingerprint = %q, want managed-subscription-key fingerprint", got)
}
}
func TestRuntimeImportServiceRepeatedImportReusesManagedResources(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
host := &fakeHostAdapter{
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "key-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"}},
}
svc := NewRuntimeImportService(store, host)
request := RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
}
first, err := svc.Import(context.Background(), request)
if err != nil {
t.Fatalf("first Import() error = %v", err)
}
second, err := svc.Import(context.Background(), request)
if err != nil {
t.Fatalf("second Import() error = %v", err)
}
if second.BatchID <= first.BatchID {
t.Fatalf("second BatchID = %d, want > first BatchID %d", second.BatchID, first.BatchID)
}
if got := queryCount(t, store.SQLDB(), "managed_resources"); got != 3 {
t.Fatalf("managed_resources row count = %d, want 3 after reused import", got)
}
if got := queryCount(t, store.SQLDB(), "import_batches"); got != 2 {
t.Fatalf("import_batches row count = %d, want 2", got)
}
}
func TestRuntimeImportServiceResolvesHostByBaseURL(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
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"},
CompletionOK: true,
CompletionStatus: 200,
},
}
result, err := NewRuntimeImportService(store, host).Import(context.Background(), RuntimeImportRequest{
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
if result.BatchID <= 0 {
t.Fatalf("BatchID = %d, want positive id", result.BatchID)
}
}
func TestRuntimeImportServiceRejectsUnregisteredHostBaseURL(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
_, err := NewRuntimeImportService(store, &fakeHostAdapter{}).Import(context.Background(), RuntimeImportRequest{
HostBaseURL: "https://missing.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err == nil || !strings.Contains(err.Error(), `host_id is required for unregistered host_base_url "https://missing.example.com"`) {
t.Fatalf("RuntimeImportService.Import() error = %v, want unregistered host_base_url error", err)
}
}
func TestRuntimeImportServiceRejectsHostBaseURLMismatch(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
_, err := NewRuntimeImportService(store, &fakeHostAdapter{}).Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://other.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: sampleProviderManifest(),
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err == nil || err.Error() != `host "host-1" base_url mismatch: registered=https://sub2api.example.com runtime=https://other.example.com` {
t.Fatalf("RuntimeImportService.Import() error = %v, want base_url mismatch", err)
}
}
func TestRuntimeImportServiceImportReconcilesExistingChannelConfiguration(t *testing.T) {
store := openProvisionTestStore(t)
defer closeProvisionTestStore(t, store)
seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
host := &fakeHostAdapter{
batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "minimax-01"}},
testResults: map[string]sub2api.ProbeResult{
"account_1": {OK: true, Status: "passed"},
},
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"}},
managedSnapshot: sub2api.ManagedResourceSnapshot{
Groups: []sub2api.NamedResource{{ID: "group_existing", Name: "MiniMax 默认分组-self-service"}},
Channels: []sub2api.NamedResource{{ID: "channel_existing", Name: "MiniMax 默认渠道-self-service"}},
Plans: []sub2api.NamedResource{{ID: "plan_existing", Name: "MiniMax 默认套餐-self-service"}},
},
}
provider := sampleProviderManifest()
provider.ProviderID = "minimax"
provider.DisplayName = "MiniMax OpenAI Compatible"
provider.BaseURL = "https://v2.aicodee.com/v1"
provider.DefaultModels = []string{"MiniMax-M2.5-highspeed", "MiniMax-M2.7-highspeed"}
provider.SmokeTestModel = "MiniMax-M2.7-highspeed"
provider.GroupTemplate.Name = "MiniMax 默认分组"
provider.ChannelTemplate = pack.ChannelTemplate{
Name: "MiniMax 默认渠道",
ModelMapping: map[string]string{"MiniMax-M2.5-highspeed": "MiniMax-M2.5-highspeed", "MiniMax-M2.7-highspeed": "MiniMax-M2.7-highspeed"},
}
provider.PlanTemplate.Name = "MiniMax 默认套餐"
svc := NewRuntimeImportService(store, host)
result, err := svc.Import(context.Background(), RuntimeImportRequest{
HostID: "host-1",
HostBaseURL: "https://sub2api.example.com",
Pack: pack.LoadedPack{
Manifest: pack.Manifest{PackID: "openai-cn-pack", Version: "1.0.0", TargetHost: "sub2api", MinHostVersion: "0.1.126", MaxHostVersion: "0.2.x"},
Checksum: "checksum-1",
},
Provider: provider,
Mode: ImportModePartial,
Keys: []string{"key-1"},
Access: AccessRequest{
Mode: AccessModeSelfService,
ProbeAPIKey: "user-key",
},
})
if err != nil {
t.Fatalf("RuntimeImportService.Import() error = %v", err)
}
if result.Report.Channel.ID != "channel_existing" {
t.Fatalf("Channel.ID = %q, want reused channel_existing", result.Report.Channel.ID)
}
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))
}
if got := host.updateChannelReq.ModelPricing[0].Models; len(got) != 2 || got[0] != "MiniMax-M2.5-highspeed" || got[1] != "MiniMax-M2.7-highspeed" {
t.Fatalf("UpdateChannel().ModelPricing[0].Models = %v, want minimax default models", got)
}
if host.updateChannelReq.ModelPricing[0].BillingMode != "token" {
t.Fatalf("UpdateChannel().ModelPricing[0].BillingMode = %q, want token", host.updateChannelReq.ModelPricing[0].BillingMode)
}
}
func openProvisionTestStore(t *testing.T) *sqlite.DB {
t.Helper()
return testutil.OpenSQLiteStore(t, testutil.SQLiteTestDSN(t, "state.db", true))
}
func closeProvisionTestStore(t *testing.T, store *sqlite.DB) {
t.Helper()
testutil.CloseSQLiteStore(t, store)
}
func seedProvisionHost(t *testing.T, store *sqlite.DB, hostID, baseURL string) int64 {
t.Helper()
id, err := store.Hosts().Create(context.Background(), sqlite.Host{
HostID: hostID,
BaseURL: baseURL,
HostVersion: "0.1.126",
AuthType: "apikey",
AuthToken: "test-host-token",
})
if err != nil {
t.Fatalf("Hosts().Create() error = %v", err)
}
return id
}
func queryCount(t *testing.T, db *sql.DB, table string) int {
t.Helper()
var count int
if err := db.QueryRowContext(context.Background(), "SELECT COUNT(*) FROM "+table).Scan(&count); err != nil {
t.Fatalf("count rows for %s: %v", table, err)
}
return count
}