fix: harden operations and configuration recovery
This commit is contained in:
@@ -83,6 +83,45 @@ func (t *coordinatedOperationTarget) SetDesired(_ context.Context, enabled bool,
|
||||
return target.OperationResult{Inspection: target.Inspection{State: t.state, Evidence: "completed"}}
|
||||
}
|
||||
|
||||
type cancellableOperationTarget struct{ started chan struct{} }
|
||||
|
||||
func (t *cancellableOperationTarget) ID() string { return "cancellable" }
|
||||
func (t *cancellableOperationTarget) Description() target.Description {
|
||||
return target.Description{Name: "Cancellable"}
|
||||
}
|
||||
func (t *cancellableOperationTarget) Availability(context.Context) target.Availability {
|
||||
return target.Availability{Available: true}
|
||||
}
|
||||
func (t *cancellableOperationTarget) Inspect(context.Context, *profile.Profile) target.Inspection {
|
||||
return target.Inspection{State: target.Disabled, Evidence: "not changed"}
|
||||
}
|
||||
func (t *cancellableOperationTarget) Confirmation(bool) string { return "" }
|
||||
func (t *cancellableOperationTarget) SetDesired(ctx context.Context, _ bool, _ *profile.Profile) target.OperationResult {
|
||||
close(t.started)
|
||||
<-ctx.Done()
|
||||
return target.OperationResult{Inspection: target.Inspection{State: target.Disabled}, Err: ctx.Err()}
|
||||
}
|
||||
|
||||
func TestInteractiveOperationCancellationReachesTarget(t *testing.T) {
|
||||
adapter := &cancellableOperationTarget{started: make(chan struct{})}
|
||||
application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
createAndActivate(t, application)
|
||||
refreshApplicationTargets(t, application)
|
||||
operation := application.StartOperation(app.Intent{Kind: app.EnableTarget, TargetID: adapter.ID()})
|
||||
<-adapter.started
|
||||
operation.Cancel()
|
||||
var final app.OperationUpdate
|
||||
for update := range operation.Updates {
|
||||
final = update
|
||||
}
|
||||
if !final.IsFailed() || !errors.Is(final.Err(), context.Canceled) {
|
||||
t.Fatalf("cancelled operation final update = %#v", final)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInteractiveOperationStreamSerializesMutationAndPublishesStructuredProgress(t *testing.T) {
|
||||
adapter := &coordinatedOperationTarget{id: "shell", state: target.Disabled, started: make(chan struct{}), release: make(chan struct{})}
|
||||
application, err := app.NewWithTargets(profile.NewFileStore(filepath.Join(t.TempDir(), "profiles.json")), []target.Target{adapter})
|
||||
@@ -224,9 +263,22 @@ func TestInteractiveOperationStreamUsesStructuredConfirmation(t *testing.T) {
|
||||
operation := application.StartOperation(app.Intent{Kind: app.EnableTarget, TargetID: adapter.ID()})
|
||||
update := <-operation.Updates
|
||||
confirmation := update.Confirmation()
|
||||
if !update.IsConfirmation() || confirmation == nil || confirmation.Title == "" || confirmation.CancelLabel == "" {
|
||||
if !update.IsConfirmation() || confirmation == nil || confirmation.Title == "" || confirmation.CancelLabel == "" || confirmation.Token == "" {
|
||||
t.Fatalf("confirmation update = %#v", update)
|
||||
}
|
||||
// Retry immediately after the terminal confirmation, before observing the
|
||||
// first stream close. The Application gate must already be released.
|
||||
retry := application.StartOperation(app.Intent{Kind: app.EnableTarget, TargetID: adapter.ID(), Confirm: true, ConfirmationToken: confirmation.Token})
|
||||
var final app.OperationUpdate
|
||||
for next := range retry.Updates {
|
||||
if next.Decode().Kind == app.OperationClosed {
|
||||
t.Fatalf("immediate confirmed retry observed stale operation gate: %#v", next)
|
||||
}
|
||||
final = next
|
||||
}
|
||||
if !final.IsSucceeded() {
|
||||
t.Fatalf("confirmed retry final update = %#v", final)
|
||||
}
|
||||
if _, open := <-operation.Updates; open {
|
||||
t.Fatal("confirmation operation stream did not close")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user