Files
sub2api-cn-relay-manager/internal/batch/service_test.go
2026-05-22 14:41:12 +08:00

297 lines
9.8 KiB
Go

package batch
import (
"context"
"encoding/json"
"testing"
"sub2api-cn-relay-manager/internal/probe"
"sub2api-cn-relay-manager/internal/store/sqlite"
)
func TestBatchImport_StartRun(t *testing.T) {
t.Parallel()
t.Run("creates run items and backfills legacy linkage after provision", func(t *testing.T) {
t.Parallel()
runStore := &fakeRunStore{}
itemStore := &fakeItemStore{}
provisioner := &fakeProvisioner{
provisionResult: ProvisionResult{
LegacyBatchID: int64Ptr(81),
LegacyProviderID: "legacy-provider",
},
}
service := BatchImportService{
RunStore: runStore,
ItemStore: itemStore,
ProbeModels: func(context.Context, string, string) (*probe.ModelsResult, error) {
return &probe.ModelsResult{RawModels: []string{"deepseek-ai/DeepSeek-V4-Pro"}}, nil
},
ProbeCapabilities: func(context.Context, string, string, []string) (*probe.CapabilityProfile, error) {
return &probe.CapabilityProfile{
TransportProfile: probe.TransportProfile{SupportsOpenAIChatCompletions: true},
ModelProfiles: []probe.ModelCapabilityProfile{
{RawModelID: "deepseek-ai/DeepSeek-V4-Pro", CanonicalModelFamily: "deepseek-v4-pro", SmokeChatOK: true},
},
}, nil
},
InspectReuse: func(context.Context, ReuseLookupInput) (ReuseLookupResult, error) {
return ReuseLookupResult{}, nil
},
Provisioner: provisioner,
}
result, err := service.StartRun(context.Background(), BatchImportRunRequest{
RunID: "run-1",
Mode: "strict",
AccessMode: "subscription",
HostID: "host-1",
HostBaseURL: "https://relay.example.com",
Entries: []BatchImportEntry{
{BaseURL: "https://api.deepseek.com/v1", APIKey: "sk-live", RequestedModels: []string{"DeepSeek V4 Pro"}},
},
})
if err != nil {
t.Fatalf("StartRun() error = %v", err)
}
if result.RunID != "run-1" {
t.Fatalf("RunID = %q, want run-1", result.RunID)
}
if len(runStore.created) != 1 || runStore.created[0].RunID != "run-1" {
t.Fatalf("created runs = %#v, want run-1 persisted", runStore.created)
}
if provisioner.provisionCalls != 1 {
t.Fatalf("provision calls = %d, want 1", provisioner.provisionCalls)
}
if len(itemStore.upserts) != 2 {
t.Fatalf("item upserts = %d, want initial + final", len(itemStore.upserts))
}
finalItem := itemStore.upserts[len(itemStore.upserts)-1]
if finalItem.LegacyBatchID == nil || *finalItem.LegacyBatchID != 81 {
t.Fatalf("LegacyBatchID = %#v, want 81", finalItem.LegacyBatchID)
}
if finalItem.LegacyProviderID != "legacy-provider" {
t.Fatalf("LegacyProviderID = %q, want legacy-provider", finalItem.LegacyProviderID)
}
if finalItem.CurrentStage != string(ItemStageConfirm) {
t.Fatalf("CurrentStage = %q, want confirm", finalItem.CurrentStage)
}
})
t.Run("active duplicate account is reused without provision", func(t *testing.T) {
t.Parallel()
providerID := NormalizeProviderID("https://api.kimi.com/v1")
itemStore := &fakeItemStore{}
provisioner := &fakeProvisioner{}
service := BatchImportService{
RunStore: &fakeRunStore{},
ItemStore: itemStore,
Provisioner: provisioner,
ProbeModels: func(context.Context, string, string) (*probe.ModelsResult, error) {
return &probe.ModelsResult{RawModels: []string{"kimi-k2.6"}}, nil
},
ProbeCapabilities: func(context.Context, string, string, []string) (*probe.CapabilityProfile, error) {
return &probe.CapabilityProfile{
ModelProfiles: []probe.ModelCapabilityProfile{{RawModelID: "kimi-k2.6", CanonicalModelFamily: "kimi-2.6", SmokeChatOK: true}},
}, nil
},
InspectReuse: func(context.Context, ReuseLookupInput) (ReuseLookupResult, error) {
return ReuseLookupResult{
ExistingProviderID: providerID,
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"kimi 2.6"},
MatchedAccountID: 201,
MatchedAccountState: MatchedAccountStateActive,
}, nil
},
}
_, err := service.StartRun(context.Background(), BatchImportRunRequest{
RunID: "run-2",
Mode: "strict",
AccessMode: "subscription",
Entries: []BatchImportEntry{
{BaseURL: "https://api.kimi.com/v1", APIKey: "sk-live", RequestedModels: []string{"kimi 2.6"}},
},
})
if err != nil {
t.Fatalf("StartRun() error = %v", err)
}
if provisioner.provisionCalls != 0 {
t.Fatalf("provision calls = %d, want 0", provisioner.provisionCalls)
}
finalItem := itemStore.upserts[len(itemStore.upserts)-1]
if !finalItem.ProvisionReused {
t.Fatal("ProvisionReused = false, want true")
}
if finalItem.MatchedAccountState != string(MatchedAccountStateActive) {
t.Fatalf("MatchedAccountState = %q, want active", finalItem.MatchedAccountState)
}
if finalItem.AccountResolution != string(AccountResolutionReused) {
t.Fatalf("AccountResolution = %q, want reused", finalItem.AccountResolution)
}
})
t.Run("deprecated duplicate account becomes reactivated", func(t *testing.T) {
t.Parallel()
providerID := NormalizeProviderID("https://api.kimi.com/v1")
itemStore := &fakeItemStore{}
service := BatchImportService{
RunStore: &fakeRunStore{},
ItemStore: itemStore,
Provisioner: &fakeProvisioner{},
ProbeModels: func(context.Context, string, string) (*probe.ModelsResult, error) {
return &probe.ModelsResult{RawModels: []string{"kimi-k2.6"}}, nil
},
ProbeCapabilities: func(context.Context, string, string, []string) (*probe.CapabilityProfile, error) {
return &probe.CapabilityProfile{
ModelProfiles: []probe.ModelCapabilityProfile{{RawModelID: "kimi-k2.6", CanonicalModelFamily: "kimi-2.6", SmokeChatOK: true}},
}, nil
},
InspectReuse: func(context.Context, ReuseLookupInput) (ReuseLookupResult, error) {
return ReuseLookupResult{
ExistingProviderID: providerID,
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"kimi-2.6"},
MatchedAccountID: 301,
MatchedAccountState: MatchedAccountStateDeprecated,
}, nil
},
}
_, err := service.StartRun(context.Background(), BatchImportRunRequest{
RunID: "run-3",
Mode: "strict",
AccessMode: "subscription",
Entries: []BatchImportEntry{
{BaseURL: "https://api.kimi.com/v1", APIKey: "sk-live", RequestedModels: []string{"kimi 2.6"}},
},
})
if err != nil {
t.Fatalf("StartRun() error = %v", err)
}
finalItem := itemStore.upserts[len(itemStore.upserts)-1]
if finalItem.AccountResolution != string(AccountResolutionReactivated) {
t.Fatalf("AccountResolution = %q, want reactivated", finalItem.AccountResolution)
}
if !finalItem.ProvisionReused {
t.Fatal("ProvisionReused = false, want true")
}
})
t.Run("same family new alias only patches mapping", func(t *testing.T) {
t.Parallel()
providerID := NormalizeProviderID("https://api.kimi.com/v1")
itemStore := &fakeItemStore{}
provisioner := &fakeProvisioner{}
service := BatchImportService{
RunStore: &fakeRunStore{},
ItemStore: itemStore,
Provisioner: provisioner,
ProbeModels: func(context.Context, string, string) (*probe.ModelsResult, error) {
return &probe.ModelsResult{RawModels: []string{"Kimi-K2.6"}}, nil
},
ProbeCapabilities: func(context.Context, string, string, []string) (*probe.CapabilityProfile, error) {
return &probe.CapabilityProfile{
ModelProfiles: []probe.ModelCapabilityProfile{{RawModelID: "Kimi-K2.6", CanonicalModelFamily: "kimi-2.6", SmokeChatOK: true}},
}, nil
},
InspectReuse: func(context.Context, ReuseLookupInput) (ReuseLookupResult, error) {
return ReuseLookupResult{
ExistingProviderID: providerID,
ExistingAccessStatus: AccessStatusActive,
ExistingCanonicalFamilys: []string{"kimi 2.6"},
MatchedAccountID: 401,
MatchedAccountState: MatchedAccountStateActive,
ExistingModelMapping: map[string]string{"kimi-k2.6": "kimi-2.6"},
}, nil
},
}
_, err := service.StartRun(context.Background(), BatchImportRunRequest{
RunID: "run-4",
Mode: "strict",
AccessMode: "subscription",
Entries: []BatchImportEntry{
{BaseURL: "https://api.kimi.com/v1", APIKey: "sk-live", RequestedModels: []string{"kimi 2.6"}},
},
})
if err != nil {
t.Fatalf("StartRun() error = %v", err)
}
if provisioner.provisionCalls != 0 {
t.Fatalf("provision calls = %d, want 0", provisioner.provisionCalls)
}
if provisioner.patchCalls != 1 {
t.Fatalf("patch calls = %d, want 1", provisioner.patchCalls)
}
if provisioner.lastPatch.Contract.ModelMapping["Kimi-K2.6"] != "kimi-2.6" {
t.Fatalf("patch mapping = %#v, want raw alias mapped to canonical family", provisioner.lastPatch.Contract.ModelMapping)
}
finalItem := itemStore.upserts[len(itemStore.upserts)-1]
if !finalItem.ProvisionReused {
t.Fatal("ProvisionReused = false, want true for patch-only flow")
}
})
}
type fakeRunStore struct {
created []sqlite.ImportRun
}
func (f *fakeRunStore) Create(ctx context.Context, run sqlite.ImportRun) error {
f.created = append(f.created, run)
return nil
}
type fakeItemStore struct {
upserts []sqlite.ImportRunItem
}
func (f *fakeItemStore) Upsert(ctx context.Context, item sqlite.ImportRunItem) error {
f.upserts = append(f.upserts, item)
return nil
}
type fakeProvisioner struct {
provisionCalls int
patchCalls int
provisionResult ProvisionResult
lastPatch PatchProvisionRequest
}
func (f *fakeProvisioner) Provision(ctx context.Context, req ProvisionRequest) (ProvisionResult, error) {
f.provisionCalls++
return f.provisionResult, nil
}
func (f *fakeProvisioner) Patch(ctx context.Context, req PatchProvisionRequest) error {
f.patchCalls++
f.lastPatch = req
return nil
}
func int64Ptr(value int64) *int64 {
return &value
}
func mustJSON(t *testing.T, value any) string {
t.Helper()
payload, err := json.Marshal(value)
if err != nil {
t.Fatalf("json.Marshal() error = %v", err)
}
return string(payload)
}