Add safe artifact migration and status fixes
This commit is contained in:
@@ -167,7 +167,10 @@ func TestDeriveProviderStatus(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{name: "recovered success beats stale reconcile", batchStatus: BatchStatusSucceeded, accessStatus: AccessStatusSelfServiceReady, reconcileStatus: "degraded", want: ProviderStatusActive},
|
||||
{name: "partial ready with active reconcile becomes active", batchStatus: BatchStatusPartial, accessStatus: AccessStatusSubscriptionReady, reconcileStatus: ProviderStatusActive, want: ProviderStatusActive},
|
||||
{name: "succeeded batch", batchStatus: BatchStatusSucceeded, reconcileStatus: "not_run", want: ProviderStatusActive},
|
||||
{name: "rolled back beats active reconcile", batchStatus: BatchStatusRolledBack, reconcileStatus: ProviderStatusActive, want: ProviderStatusFailed},
|
||||
{name: "failed beats active reconcile", batchStatus: BatchStatusFailed, reconcileStatus: ProviderStatusActive, want: ProviderStatusFailed},
|
||||
{name: "failed batch", batchStatus: BatchStatusFailed, want: ProviderStatusFailed},
|
||||
{name: "running batch", batchStatus: "running", want: "running"},
|
||||
{name: "unknown fallback", batchStatus: " pending ", want: "pending"},
|
||||
|
||||
@@ -168,21 +168,25 @@ func (s *ProviderStatusService) resolveHostAndBatch(ctx context.Context, provide
|
||||
}
|
||||
|
||||
func deriveProviderStatus(batchStatus, accessStatus, reconcileStatus string) string {
|
||||
batchStatus = strings.TrimSpace(batchStatus)
|
||||
reconcileStatus = strings.TrimSpace(reconcileStatus)
|
||||
accessStatus = strings.TrimSpace(accessStatus)
|
||||
if strings.TrimSpace(batchStatus) == BatchStatusSucceeded && accessStatus != "" && accessStatus != AccessStatusBroken {
|
||||
switch batchStatus {
|
||||
case BatchStatusFailed, BatchStatusRolledBack:
|
||||
return ProviderStatusFailed
|
||||
}
|
||||
if (batchStatus == BatchStatusSucceeded || batchStatus == BatchStatusPartial) && accessStatus != "" && accessStatus != AccessStatusBroken && reconcileStatus == ProviderStatusActive {
|
||||
return ProviderStatusActive
|
||||
}
|
||||
if batchStatus == BatchStatusSucceeded && accessStatus != "" && accessStatus != AccessStatusBroken {
|
||||
return ProviderStatusActive
|
||||
}
|
||||
if reconcileStatus != "" && reconcileStatus != "not_run" {
|
||||
return reconcileStatus
|
||||
}
|
||||
switch strings.TrimSpace(batchStatus) {
|
||||
switch batchStatus {
|
||||
case BatchStatusSucceeded:
|
||||
return ProviderStatusActive
|
||||
case BatchStatusFailed:
|
||||
return ProviderStatusFailed
|
||||
case BatchStatusRolledBack:
|
||||
return ProviderStatusFailed
|
||||
case "running":
|
||||
return "running"
|
||||
default:
|
||||
|
||||
@@ -289,3 +289,87 @@ func TestProviderStatusServiceFailsOnInvalidReconcileSummary(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user