Expand coverage for runtime and sqlite paths
This commit is contained in:
@@ -671,6 +671,7 @@ type fakeHostAdapter struct {
|
||||
testedModels map[string]string
|
||||
disableResponsesCalls int
|
||||
disabledResponsesAccountIDs []string
|
||||
deleteErrors map[string]error
|
||||
}
|
||||
|
||||
func (f *fakeHostAdapter) GetHostVersion(context.Context) (string, error) {
|
||||
@@ -688,6 +689,9 @@ func (f *fakeHostAdapter) CreateGroup(_ context.Context, req sub2api.CreateGroup
|
||||
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
|
||||
}
|
||||
@@ -703,6 +707,9 @@ func (f *fakeHostAdapter) UpdateChannel(_ context.Context, channelID string, 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
|
||||
}
|
||||
@@ -711,6 +718,9 @@ func (f *fakeHostAdapter) CreatePlan(context.Context, sub2api.CreatePlanRequest)
|
||||
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
|
||||
}
|
||||
@@ -725,6 +735,9 @@ func (f *fakeHostAdapter) BatchCreateAccounts(_ context.Context, req sub2api.Bat
|
||||
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
|
||||
|
||||
@@ -188,3 +188,103 @@ func TestProviderStatusServiceRequiresPackIDWhenProviderIDIsAmbiguous(t *testing
|
||||
t.Fatalf("GetStatus() error = %v, want ambiguous provider error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderStatusServiceRequiresLatestBatchWhenProviderHasNoImports(t *testing.T) {
|
||||
store := openProvisionTestStore(t)
|
||||
defer closeProvisionTestStore(t, store)
|
||||
|
||||
ctx := context.Background()
|
||||
packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
|
||||
if err != nil {
|
||||
t.Fatalf("Packs().Create() error = %v", err)
|
||||
}
|
||||
if _, err := store.Providers().Create(ctx, sqlite.Provider{
|
||||
PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
|
||||
}); err != nil {
|
||||
t.Fatalf("Providers().Create() error = %v", err)
|
||||
}
|
||||
|
||||
_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek"})
|
||||
if err == nil || err.Error() != "latest import batch not found for provider" {
|
||||
t.Fatalf("GetStatus() error = %v, want latest batch not found", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderStatusServiceRequiresHostWhenProviderExistsOnMultipleHosts(t *testing.T) {
|
||||
store := openProvisionTestStore(t)
|
||||
defer closeProvisionTestStore(t, store)
|
||||
|
||||
ctx := context.Background()
|
||||
hostA, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-a", BaseURL: "https://a.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
|
||||
if err != nil {
|
||||
t.Fatalf("Hosts().Create(host-a) error = %v", err)
|
||||
}
|
||||
hostB, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-b", BaseURL: "https://b.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
|
||||
if err != nil {
|
||||
t.Fatalf("Hosts().Create(host-b) error = %v", err)
|
||||
}
|
||||
packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
|
||||
if err != nil {
|
||||
t.Fatalf("Packs().Create() error = %v", err)
|
||||
}
|
||||
providerID, err := store.Providers().Create(ctx, sqlite.Provider{
|
||||
PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Providers().Create() error = %v", err)
|
||||
}
|
||||
if _, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
|
||||
HostID: hostA, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady,
|
||||
}); err != nil {
|
||||
t.Fatalf("ImportBatches().Create(host-a) error = %v", err)
|
||||
}
|
||||
if _, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
|
||||
HostID: hostB, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady,
|
||||
}); err != nil {
|
||||
t.Fatalf("ImportBatches().Create(host-b) error = %v", err)
|
||||
}
|
||||
|
||||
_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek"})
|
||||
if err == nil || err.Error() != "provider exists on multiple hosts; host_id is required" {
|
||||
t.Fatalf("GetStatus() error = %v, want host_id required", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderStatusServiceFailsOnInvalidReconcileSummary(t *testing.T) {
|
||||
store := openProvisionTestStore(t)
|
||||
defer closeProvisionTestStore(t, store)
|
||||
|
||||
ctx := context.Background()
|
||||
hostID := seedProvisionHost(t, store, "host-1", "https://sub2api.example.com")
|
||||
packID, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0", Checksum: "checksum-1"})
|
||||
if err != nil {
|
||||
t.Fatalf("Packs().Create() error = %v", err)
|
||||
}
|
||||
providerID, err := store.Providers().Create(ctx, sqlite.Provider{
|
||||
PackID: packID, ProviderID: "deepseek", DisplayName: "DeepSeek", BaseURL: "https://api.deepseek.com", Platform: "openai",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Providers().Create() error = %v", err)
|
||||
}
|
||||
batchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{
|
||||
HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ImportBatches().Create() error = %v", err)
|
||||
}
|
||||
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
|
||||
BatchID: batchID, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: `{"ok":true}`,
|
||||
}); err != nil {
|
||||
t.Fatalf("AccessClosures().Create() error = %v", err)
|
||||
}
|
||||
if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{
|
||||
BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: "active", SummaryJSON: `{"missing_count":`,
|
||||
}); err != nil {
|
||||
t.Fatalf("ReconcileRuns().Create() error = %v", err)
|
||||
}
|
||||
|
||||
_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
||||
if err == nil || err.Error() == "" {
|
||||
t.Fatal("GetStatus() error = nil, want decode reconcile summary failure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package provision
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sub2api-cn-relay-manager/internal/host/sub2api"
|
||||
@@ -69,3 +71,58 @@ func TestRollbackServiceRollbackStoredResourcesDeletesOnlyProvidedIDs(t *testing
|
||||
t.Fatalf("deleted resources = %#v, want %#v", host.deletedResources, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollbackServiceRequiresHost(t *testing.T) {
|
||||
svc := NewRollbackService(nil)
|
||||
|
||||
if _, err := svc.Rollback(context.Background(), RollbackRequest{Provider: sampleProviderManifest()}); err == nil || err.Error() != "rollback host is required" {
|
||||
t.Fatalf("Rollback() error = %v, want rollback host is required", err)
|
||||
}
|
||||
if _, err := svc.RollbackStoredResources(context.Background(), nil); err == nil || err.Error() != "rollback host is required" {
|
||||
t.Fatalf("RollbackStoredResources() error = %v, want rollback host is required", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollbackServiceCollectsDeleteErrors(t *testing.T) {
|
||||
host := &fakeHostAdapter{
|
||||
managedSnapshot: sub2api.ManagedResourceSnapshot{
|
||||
Groups: []sub2api.NamedResource{{ID: "group_1", Name: "DeepSeek 默认分组"}},
|
||||
Channels: []sub2api.NamedResource{{ID: "channel_1", Name: "DeepSeek 默认渠道"}},
|
||||
Plans: []sub2api.NamedResource{{ID: "plan_1", Name: "DeepSeek 默认套餐"}},
|
||||
Accounts: []sub2api.NamedResource{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}},
|
||||
},
|
||||
deleteErrors: map[string]error{
|
||||
"account:account_2": errors.New("account blocked"),
|
||||
"channel:channel_1": errors.New("channel blocked"),
|
||||
},
|
||||
}
|
||||
|
||||
report, err := NewRollbackService(host).Rollback(context.Background(), RollbackRequest{Provider: sampleProviderManifest()})
|
||||
if err == nil {
|
||||
t.Fatal("Rollback() error = nil, want joined delete errors")
|
||||
}
|
||||
if report.AccountsDeleted != 1 || report.PlansDeleted != 1 || report.ChannelsDeleted != 0 || report.GroupsDeleted != 1 {
|
||||
t.Fatalf("Rollback() report = %+v, want partial success counts", report)
|
||||
}
|
||||
if !strings.Contains(err.Error(), "delete account account_2") || !strings.Contains(err.Error(), "delete channel channel_1") {
|
||||
t.Fatalf("Rollback() error = %v, want joined account/channel errors", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamedResourceSnapshotFromStoredIgnoresUnknownTypes(t *testing.T) {
|
||||
snapshot := namedResourceSnapshotFromStored([]sqlite.ManagedResource{
|
||||
{ResourceType: "group", HostResourceID: "group_1", ResourceName: "g1"},
|
||||
{ResourceType: "mystery", HostResourceID: "mystery_1", ResourceName: "m1"},
|
||||
{ResourceType: "account", HostResourceID: "account_1", ResourceName: "a1"},
|
||||
})
|
||||
|
||||
if len(snapshot.Groups) != 1 || snapshot.Groups[0].ID != "group_1" {
|
||||
t.Fatalf("snapshot.Groups = %#v, want group_1 only", snapshot.Groups)
|
||||
}
|
||||
if len(snapshot.Accounts) != 1 || snapshot.Accounts[0].ID != "account_1" {
|
||||
t.Fatalf("snapshot.Accounts = %#v, want account_1 only", snapshot.Accounts)
|
||||
}
|
||||
if len(snapshot.Plans) != 0 || len(snapshot.Channels) != 0 {
|
||||
t.Fatalf("snapshot = %#v, want unknown type ignored", snapshot)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -514,6 +514,101 @@ func TestRuntimeImportServiceRepeatedImportReusesManagedResources(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user