2026-05-15 19:26:25 +08:00
|
|
|
package provision
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-05-23 11:12:34 +08:00
|
|
|
"strings"
|
2026-05-15 19:26:25 +08:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"sub2api-cn-relay-manager/internal/store/sqlite"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestProviderStatusServiceReturnsLatestSnapshot(t *testing.T) {
|
|
|
|
|
store := openProvisionTestStore(t)
|
|
|
|
|
defer closeProvisionTestStore(t, store)
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
hostID, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-1", BaseURL: "https://sub2api.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{"supports_batch_accounts":true}`})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Hosts().Create() 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)
|
|
|
|
|
}
|
|
|
|
|
batchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModeStrict, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ImportBatches().Create() error = %v", err)
|
|
|
|
|
}
|
2026-05-18 22:22:22 +08:00
|
|
|
if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: batchID, HostID: hostID, ResourceType: "group", HostResourceID: "group-1", ResourceName: "deepseek-group"}); err != nil {
|
2026-05-15 19:26:25 +08:00
|
|
|
t.Fatalf("ManagedResources().Create(group) error = %v", err)
|
|
|
|
|
}
|
2026-05-18 22:22:22 +08:00
|
|
|
if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: batchID, HostID: hostID, ResourceType: "account", HostResourceID: "account-1", ResourceName: "deepseek-account-1"}); err != nil {
|
2026-05-15 19:26:25 +08:00
|
|
|
t.Fatalf("ManagedResources().Create(account) 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)
|
|
|
|
|
}
|
2026-05-18 22:22:22 +08:00
|
|
|
if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: "drifted", SummaryJSON: `{"missing_count":1}`}); err != nil {
|
2026-05-15 19:26:25 +08:00
|
|
|
t.Fatalf("ReconcileRuns().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 22:22:22 +08:00
|
|
|
snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
2026-05-15 19:26:25 +08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.Host.HostID != "host-1" {
|
|
|
|
|
t.Fatalf("Host.HostID = %q, want host-1", snapshot.Host.HostID)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.Pack.PackID != "openai-cn-pack" {
|
|
|
|
|
t.Fatalf("Pack.PackID = %q, want openai-cn-pack", snapshot.Pack.PackID)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.Provider.ProviderID != "deepseek" {
|
|
|
|
|
t.Fatalf("Provider.ProviderID = %q, want deepseek", snapshot.Provider.ProviderID)
|
|
|
|
|
}
|
2026-05-20 22:09:40 +08:00
|
|
|
if snapshot.ProviderStatus != ProviderStatusActive {
|
|
|
|
|
t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive)
|
2026-05-15 19:26:25 +08:00
|
|
|
}
|
|
|
|
|
if snapshot.LatestAccessStatus != AccessStatusSelfServiceReady {
|
|
|
|
|
t.Fatalf("LatestAccessStatus = %q, want %q", snapshot.LatestAccessStatus, AccessStatusSelfServiceReady)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.LatestReconcileStatus != "drifted" {
|
|
|
|
|
t.Fatalf("LatestReconcileStatus = %q, want drifted", snapshot.LatestReconcileStatus)
|
|
|
|
|
}
|
|
|
|
|
if got := len(snapshot.ManagedResources); got != 2 {
|
|
|
|
|
t.Fatalf("len(ManagedResources) = %d, want 2", got)
|
|
|
|
|
}
|
|
|
|
|
if got, ok := snapshot.LatestReconcileSummary["missing_count"].(float64); !ok || got != 1 {
|
|
|
|
|
t.Fatalf("LatestReconcileSummary[missing_count] = %#v, want 1", snapshot.LatestReconcileSummary["missing_count"])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-19 20:21:21 +08:00
|
|
|
func TestProviderStatusServiceIncludesManagedResourcesFromLatestBatchOnly(t *testing.T) {
|
2026-05-18 22:22:22 +08:00
|
|
|
store := openProvisionTestStore(t)
|
|
|
|
|
defer closeProvisionTestStore(t, store)
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
hostID, err := store.Hosts().Create(ctx, sqlite.Host{HostID: "host-1", BaseURL: "https://sub2api.example.com", HostVersion: "0.1.126", CapabilityProbeJSON: `{}`})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Hosts().Create() 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)
|
|
|
|
|
}
|
|
|
|
|
firstBatch, 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(first) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: firstBatch, HostID: hostID, ResourceType: "group", HostResourceID: "group-1", ResourceName: "deepseek-group"}); err != nil {
|
|
|
|
|
t.Fatalf("ManagedResources().Create(group) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
secondBatch, 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(second) error = %v", err)
|
|
|
|
|
}
|
2026-05-19 20:21:21 +08:00
|
|
|
if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: secondBatch, HostID: hostID, ResourceType: "channel", HostResourceID: "channel-2", ResourceName: "deepseek-channel"}); err != nil {
|
|
|
|
|
t.Fatalf("ManagedResources().Create(channel) error = %v", err)
|
|
|
|
|
}
|
2026-05-18 22:22:22 +08:00
|
|
|
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: secondBatch, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: `{"ok":true}`}); err != nil {
|
|
|
|
|
t.Fatalf("AccessClosures().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if got := len(snapshot.ManagedResources); got != 1 {
|
2026-05-19 20:21:21 +08:00
|
|
|
t.Fatalf("len(ManagedResources) = %d, want latest batch resources only", got)
|
2026-05-18 22:22:22 +08:00
|
|
|
}
|
2026-05-19 20:21:21 +08:00
|
|
|
if snapshot.ManagedResources[0].HostResourceID != "channel-2" {
|
|
|
|
|
t.Fatalf("HostResourceID = %q, want channel-2", snapshot.ManagedResources[0].HostResourceID)
|
2026-05-18 22:22:22 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProviderStatusServiceScopesReconcileRunsByHost(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)
|
|
|
|
|
}
|
|
|
|
|
batchA, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostA, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ImportBatches().Create(host-a) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
batchB, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostB, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ImportBatches().Create(host-b) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchA, HostID: hostA, ProviderID: providerID, Status: "drifted", SummaryJSON: `{"host":"a"}`}); err != nil {
|
|
|
|
|
t.Fatalf("ReconcileRuns().Create(host-a) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchB, HostID: hostB, ProviderID: providerID, Status: "active", SummaryJSON: `{"host":"b"}`}); err != nil {
|
|
|
|
|
t.Fatalf("ReconcileRuns().Create(host-b) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-a"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.LatestReconcileStatus != "drifted" {
|
|
|
|
|
t.Fatalf("LatestReconcileStatus = %q, want drifted for host-a", snapshot.LatestReconcileStatus)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 19:26:25 +08:00
|
|
|
func TestProviderStatusServiceRequiresPackIDWhenProviderIDIsAmbiguous(t *testing.T) {
|
|
|
|
|
store := openProvisionTestStore(t)
|
|
|
|
|
defer closeProvisionTestStore(t, store)
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
pack1, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "pack-a", Version: "1.0.0", Checksum: "checksum-a"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Packs().Create(pack-a) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
pack2, err := store.Packs().Create(ctx, sqlite.Pack{PackID: "pack-b", Version: "1.0.0", Checksum: "checksum-b"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Packs().Create(pack-b) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.Providers().Create(ctx, sqlite.Provider{PackID: pack1, ProviderID: "deepseek", DisplayName: "DeepSeek A", BaseURL: "https://a.example.com", Platform: "openai"}); err != nil {
|
|
|
|
|
t.Fatalf("Providers().Create(pack-a) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.Providers().Create(ctx, sqlite.Provider{PackID: pack2, ProviderID: "deepseek", DisplayName: "DeepSeek B", BaseURL: "https://b.example.com", Platform: "openai"}); err != nil {
|
|
|
|
|
t.Fatalf("Providers().Create(pack-b) error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek"})
|
|
|
|
|
if err == nil || err.Error() != `provider "deepseek" exists in multiple packs; pack_id is required` {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v, want ambiguous provider error", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-23 10:55:57 +08:00
|
|
|
|
|
|
|
|
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"})
|
2026-05-23 11:12:34 +08:00
|
|
|
if err == nil || !strings.Contains(err.Error(), "decode reconcile summary") {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v, want decode reconcile summary failure", err)
|
2026-05-23 10:55:57 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-25 10:48:04 +08:00
|
|
|
|
|
|
|
|
func TestProviderStatusServicePromotesRecoveredPartialBatchAfterActiveReconcile(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: BatchStatusPartial, AccessStatus: AccessStatusSubscriptionReady,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ImportBatches().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
|
|
|
|
|
BatchID: batchID, ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady, DetailsJSON: `{"completion_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: ProviderStatusActive, SummaryJSON: `{"access_status":"subscription_ready"}`,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
t.Fatalf("ReconcileRuns().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 14:42:51 +08:00
|
|
|
snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.ProviderStatus != ProviderStatusActive {
|
|
|
|
|
t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProviderStatusServicePromotesReadyPartialBatchWithoutReconcile(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: BatchStatusPartial, AccessStatus: AccessStatusSubscriptionReady,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ImportBatches().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
|
|
|
|
|
BatchID: batchID, ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady, DetailsJSON: `{"completion_ok":true}`,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
t.Fatalf("AccessClosures().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-25 10:48:04 +08:00
|
|
|
snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.ProviderStatus != ProviderStatusActive {
|
|
|
|
|
t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProviderStatusServiceKeepsRolledBackBatchFailedEvenWithActiveReconcile(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: BatchStatusRolledBack, AccessStatus: AccessStatusSubscriptionReady,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ImportBatches().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{
|
|
|
|
|
BatchID: batchID, ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady, DetailsJSON: `{"completion_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: ProviderStatusActive, SummaryJSON: `{"access_status":"subscription_ready"}`,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
t.Fatalf("ReconcileRuns().Create() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
snapshot, err := NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("GetStatus() error = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if snapshot.ProviderStatus != ProviderStatusFailed {
|
|
|
|
|
t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusFailed)
|
|
|
|
|
}
|
|
|
|
|
}
|