package provision import ( "context" "testing" "sub2api-cn-relay-manager/internal/host/sub2api" "sub2api-cn-relay-manager/internal/store/sqlite" ) func TestBatchDetailServiceGetReturnsPersistedArtifacts(t *testing.T) { store := openProvisionTestStore(t) defer closeProvisionTestStore(t, store) host := &fakeHostAdapter{ batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}}, 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"}}, } batchID := seedRuntimeImportForReconcile(t, store, host) providerRow, err := store.Providers().ListByProviderID(context.Background(), sampleProviderManifest().ProviderID) if err != nil { t.Fatalf("Providers().ListByProviderID() error = %v", err) } if len(providerRow) != 1 { t.Fatalf("providers = %d, want 1", len(providerRow)) } batchRow, err := store.ImportBatches().GetByID(context.Background(), batchID) if err != nil { t.Fatalf("ImportBatches().GetByID() error = %v", err) } if _, err := store.ReconcileRuns().Create(context.Background(), sqlite.ReconcileRun{BatchID: batchID, HostID: batchRow.HostID, ProviderID: providerRow[0].ID, Status: "active", SummaryJSON: `{"missing_count":0}`}); err != nil { t.Fatalf("ReconcileRuns().Create() error = %v", err) } result, err := NewBatchDetailService(store).Get(context.Background(), batchID) if err != nil { t.Fatalf("Get() error = %v", err) } if result.Batch.ID != batchID { t.Fatalf("Batch.ID = %d, want %d", result.Batch.ID, batchID) } if len(result.Items) != 2 { t.Fatalf("len(Items) = %d, want 2", len(result.Items)) } if len(result.ManagedResources) != 4 { t.Fatalf("len(ManagedResources) = %d, want 4", len(result.ManagedResources)) } if len(result.AccessClosures) != 1 { t.Fatalf("len(AccessClosures) = %d, want 1", len(result.AccessClosures)) } if len(result.ReconcileRuns) != 1 { t.Fatalf("len(ReconcileRuns) = %d, want 1", len(result.ReconcileRuns)) } } func TestBatchDetailServiceScopesReconcileRunsByBatchID(t *testing.T) { store := openProvisionTestStore(t) defer closeProvisionTestStore(t, store) host := &fakeHostAdapter{ batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}}, 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"}}, } ctx := context.Background() batchID := seedRuntimeImportForReconcile(t, store, host) batchRow, err := store.ImportBatches().GetByID(ctx, batchID) if err != nil { t.Fatalf("ImportBatches().GetByID() error = %v", err) } providerRow, err := store.Providers().ListByProviderID(ctx, sampleProviderManifest().ProviderID) if err != nil { t.Fatalf("Providers().ListByProviderID() error = %v", err) } if len(providerRow) != 1 { t.Fatalf("providers = %d, want 1", len(providerRow)) } secondBatchID, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: batchRow.HostID, PackID: batchRow.PackID, ProviderID: providerRow[0].ID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSelfServiceReady}) if err != nil { t.Fatalf("ImportBatches().Create(second) error = %v", err) } if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchID, HostID: batchRow.HostID, ProviderID: providerRow[0].ID, Status: "drifted", SummaryJSON: `{"batch":"first"}`}); err != nil { t.Fatalf("ReconcileRuns().Create(first batch) error = %v", err) } if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: secondBatchID, HostID: batchRow.HostID, ProviderID: providerRow[0].ID, Status: "active", SummaryJSON: `{"batch":"second"}`}); err != nil { t.Fatalf("ReconcileRuns().Create(second batch) error = %v", err) } result, err := NewBatchDetailService(store).Get(ctx, secondBatchID) if err != nil { t.Fatalf("Get() error = %v", err) } if len(result.ReconcileRuns) != 1 { t.Fatalf("len(ReconcileRuns) = %d, want 1 for this batch only", len(result.ReconcileRuns)) } if result.ReconcileRuns[0].Status != "active" { t.Fatalf("ReconcileRuns[0].Status = %q, want active", result.ReconcileRuns[0].Status) } } func TestBatchDetailServiceGetValidatesStore(t *testing.T) { _, err := (*BatchDetailService)(nil).Get(context.Background(), 1) if err == nil || err.Error() != "store is required" { t.Fatalf("nil service Get() error = %v, want store is required", err) } } func TestAccountIDFromProbeSummary(t *testing.T) { accountID, err := accountIDFromProbeSummary(`{"account_id":" account_1 "}`) if err != nil { t.Fatalf("accountIDFromProbeSummary() error = %v", err) } if accountID != "account_1" { t.Fatalf("accountID = %q, want account_1", accountID) } if _, err := accountIDFromProbeSummary(`{`); err == nil { t.Fatal("accountIDFromProbeSummary() error = nil, want JSON decode error") } blank, err := accountIDFromProbeSummary("") if err != nil { t.Fatalf("accountIDFromProbeSummary(blank) error = %v", err) } if blank != "" { t.Fatalf("blank accountID = %q, want empty", blank) } } func TestReconcileServiceRerunAccessClosureWithoutProbeKeyUsesLatestStatus(t *testing.T) { store := openProvisionTestStore(t) defer closeProvisionTestStore(t, store) status, checked, err := NewReconcileService(store, &fakeHostAdapter{}).rerunAccessClosure(context.Background(), 1, []sqlite.AccessClosureRecord{{ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady}}, "", "deepseek-chat") if err != nil { t.Fatalf("rerunAccessClosure() error = %v", err) } if checked { t.Fatal("checked = true, want false without probe key") } if status != AccessStatusSubscriptionReady { t.Fatalf("status = %q, want %q", status, AccessStatusSubscriptionReady) } } func TestReconcileServiceRerunAccessClosureMarksBrokenWhenGatewayCheckFails(t *testing.T) { store := openProvisionTestStore(t) defer closeProvisionTestStore(t, store) hostSeed := &fakeHostAdapter{ batchAccounts: []sub2api.AccountRef{{ID: "account_1", Name: "deepseek-01"}, {ID: "account_2", Name: "deepseek-02"}}, 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"}}, } batchID := seedRuntimeImportForReconcile(t, store, hostSeed) host := &fakeHostAdapter{gatewayResult: sub2api.GatewayAccessResult{OK: false, StatusCode: 403, HasExpectedModel: false}} status, checked, err := NewReconcileService(store, host).rerunAccessClosure(context.Background(), batchID, []sqlite.AccessClosureRecord{{ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady}}, "user-key", "deepseek-chat") if err != nil { t.Fatalf("rerunAccessClosure() error = %v", err) } if !checked { t.Fatal("checked = false, want true") } if status != AccessStatusBroken { t.Fatalf("status = %q, want %q", status, AccessStatusBroken) } if got := queryCount(t, store.SQLDB(), "access_closure_records"); got != 2 { t.Fatalf("access_closure_records row count = %d, want 2 after rerun", got) } if host.gatewayProbe.ExpectedModel != "deepseek-chat" { t.Fatalf("ExpectedModel = %q, want deepseek-chat", host.gatewayProbe.ExpectedModel) } } func TestDiffManagedResourcesCountsMissingAndExtra(t *testing.T) { missing, extra := diffManagedResources( []sqlite.ManagedResource{ {ResourceType: "group", HostResourceID: "group_1"}, {ResourceType: "account", HostResourceID: "account_1"}, }, sub2api.ManagedResourceSnapshot{ Groups: []sub2api.NamedResource{{ID: "group_1"}}, Accounts: []sub2api.NamedResource{{ID: "account_2"}}, }, ) if missing != 1 || extra != 1 { t.Fatalf("diffManagedResources() = (%d, %d), want (1, 1)", missing, extra) } } func TestDeriveProviderStatus(t *testing.T) { tests := []struct { name string batchStatus string accessStatus string reconcileStatus string want string }{ {name: "recovered success beats stale reconcile", batchStatus: BatchStatusSucceeded, accessStatus: AccessStatusSelfServiceReady, reconcileStatus: "degraded", want: ProviderStatusActive}, {name: "succeeded batch", batchStatus: BatchStatusSucceeded, reconcileStatus: "not_run", want: ProviderStatusActive}, {name: "failed batch", batchStatus: BatchStatusFailed, want: ProviderStatusFailed}, {name: "running batch", batchStatus: "running", want: "running"}, {name: "unknown fallback", batchStatus: " pending ", want: "pending"}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if got := deriveProviderStatus(tc.batchStatus, tc.accessStatus, tc.reconcileStatus); got != tc.want { t.Fatalf("deriveProviderStatus(%q, %q, %q) = %q, want %q", tc.batchStatus, tc.accessStatus, tc.reconcileStatus, got, tc.want) } }) } } func TestProviderStatusServiceAggregatesLatestAccessModesAcrossBatches(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", TargetHost: "sub2api", 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) } batchSubscription, err := store.ImportBatches().Create(ctx, sqlite.ImportBatch{HostID: hostID, PackID: packID, ProviderID: providerID, Mode: ImportModePartial, BatchStatus: BatchStatusSucceeded, AccessStatus: AccessStatusSubscriptionReady}) if err != nil { t.Fatalf("ImportBatches().Create(subscription) error = %v", err) } if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: batchSubscription, ClosureType: AccessModeSubscription, Status: AccessStatusSubscriptionReady, DetailsJSON: "{}"}); err != nil { t.Fatalf("AccessClosures().Create(subscription) error = %v", err) } batchSelfService, 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(self_service) error = %v", err) } if _, err := store.AccessClosures().Create(ctx, sqlite.AccessClosureRecord{BatchID: batchSelfService, ClosureType: AccessModeSelfService, Status: AccessStatusSelfServiceReady, DetailsJSON: "{}"}); err != nil { t.Fatalf("AccessClosures().Create(self_service) error = %v", err) } if _, err := store.ReconcileRuns().Create(ctx, sqlite.ReconcileRun{BatchID: batchSelfService, 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", PackID: "openai-cn-pack", HostID: "host-1"}) if err != nil { t.Fatalf("GetStatus() error = %v", err) } if snapshot.LatestAccessStatus != AccessStatusFullyReady { t.Fatalf("LatestAccessStatus = %q, want %q", snapshot.LatestAccessStatus, AccessStatusFullyReady) } if snapshot.ProviderStatus != ProviderStatusActive { t.Fatalf("ProviderStatus = %q, want %q", snapshot.ProviderStatus, ProviderStatusActive) } if snapshot.LatestReconcileStatus != "drifted" { t.Fatalf("LatestReconcileStatus = %q, want drifted", snapshot.LatestReconcileStatus) } } func TestBuildPackAndProviderRecord(t *testing.T) { packRow, err := buildPackRecord(sampleLoadedPack()) if err != nil { t.Fatalf("buildPackRecord() error = %v", err) } if packRow.PackID != "openai-cn-pack" || packRow.TargetHost != "sub2api" { t.Fatalf("packRow = %#v, want populated pack metadata", packRow) } providerRow, err := buildProviderRecord(7, sampleProviderManifest()) if err != nil { t.Fatalf("buildProviderRecord() error = %v", err) } if providerRow.PackID != 7 || providerRow.ProviderID != sampleProviderManifest().ProviderID { t.Fatalf("providerRow = %#v, want persisted provider metadata", providerRow) } if providerRow.DefaultModelsJSON == "" || providerRow.ManifestJSON == "" { t.Fatalf("providerRow JSON fields = %#v, want serialized JSON", providerRow) } } func TestFirstNonEmptyAndFingerprintKey(t *testing.T) { if got := firstNonEmpty(" ", "value", "other"); got != "value" { t.Fatalf("firstNonEmpty() = %q, want value", got) } if got := fingerprintKey([]string{" key-1 "}, 0); got == "key-1" || got == "sha256:" || len(got) < 20 { t.Fatalf("fingerprintKey() = %q, want sha256 fingerprint", got) } if got := fingerprintKey(nil, 3); got != "key-4" { t.Fatalf("fingerprintKey(nil, 3) = %q, want key-4", got) } } func TestProviderStatusServiceGetResourcesRequiresProviderID(t *testing.T) { store := openProvisionTestStore(t) defer closeProvisionTestStore(t, store) _, err := NewProviderStatusService(store).GetResources(context.Background(), ProviderQuery{}) if err == nil || err.Error() != "provider_id is required" { t.Fatalf("GetResources() error = %v, want provider_id is required", err) } } func TestResourceSlugFallsBackToProvider(t *testing.T) { if got := resourceSlug(" !!! "); got != "provider" { t.Fatalf("resourceSlug() = %q, want provider", got) } provider := sampleProviderManifest() provider.ProviderID = " DeepSeek CN / Prod " provider.GroupTemplate.Name = "" provider.ChannelTemplate.Name = "" provider.PlanTemplate.Name = "" if got := SuggestAccountNamePrefix(provider); got != "deepseek-cn-prod-" { t.Fatalf("SuggestAccountNamePrefix() = %q, want deepseek-cn-prod-", got) } resourceNames := SuggestResourceNames(provider) if resourceNames.Group != "crm-deepseek-cn-prod-group" { t.Fatalf("SuggestResourceNames() = %#v, want slugged resource names", resourceNames) } }