package provision import ( "context" "strings" "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) } if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: batchID, HostID: hostID, ResourceType: "group", HostResourceID: "group-1", ResourceName: "deepseek-group"}); err != nil { t.Fatalf("ManagedResources().Create(group) error = %v", err) } if _, err := store.ManagedResources().Create(ctx, sqlite.ManagedResource{BatchID: batchID, HostID: hostID, ResourceType: "account", HostResourceID: "account-1", ResourceName: "deepseek-account-1"}); err != nil { 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) } if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchID, HostID: hostID, ProviderID: providerID, Status: "drifted", SummaryJSON: `{"missing_count":1}`}); 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.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) } if snapshot.ProviderStatus != ProviderStatusActive { t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive) } 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"]) } } func TestProviderStatusServiceIncludesManagedResourcesFromLatestBatchOnly(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: `{}`}) 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) } 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) } 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 { t.Fatalf("len(ManagedResources) = %d, want latest batch resources only", got) } if snapshot.ManagedResources[0].HostResourceID != "channel-2" { t.Fatalf("HostResourceID = %q, want channel-2", snapshot.ManagedResources[0].HostResourceID) } } 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) } } 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) } } 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 || !strings.Contains(err.Error(), "decode reconcile summary") { t.Fatalf("GetStatus() error = %v, want decode reconcile summary failure", err) } } 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) } 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) } }