feat: support explicit instance data deletion

This commit is contained in:
QiuSW
2026-07-27 23:16:11 +08:00
parent e400172d04
commit cab7e6ac7b
12 changed files with 670 additions and 28 deletions
+105
View File
@@ -136,6 +136,111 @@ func TestShellDeletesOnlyAfterConfirmation(t *testing.T) {
}
}
func TestShellDeletesInstanceDataBeforeRemovingConfiguration(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[3]
deletions := 0
shell.OnDeleteInstanceData(func(_ context.Context, row InstanceRow) error {
deletions++
if row.ID != target.ID || row.UserDataDir != target.UserDataDir {
t.Fatalf("delete snapshot = %#v", row)
}
return nil
}, nil)
shell.requestDelete(target.ID)
shell.deleteData.Value = true
shell.confirmDelete()
if !shell.deleteDataRunning || len(shell.rows) != 4 {
t.Fatalf("data deletion started=%v rows=%d", shell.deleteDataRunning, len(shell.rows))
}
var result instanceDataDeleteResult
select {
case result = <-shell.deleteDataResults:
case <-time.After(time.Second):
t.Fatal("data deletion did not produce a result")
}
if deletions != 1 {
t.Fatalf("deletions = %d", deletions)
}
shell.deleteDataResults <- result
shell.consumeInstanceDataDeleteResults()
if shell.pendingDeleteID != "" || shell.deleteDataRunning || len(shell.rows) != 3 {
t.Fatalf("data deletion state = pending %q running=%v rows=%d", shell.pendingDeleteID, shell.deleteDataRunning, len(shell.rows))
}
if _, exists := shell.instanceRow(target.ID); exists || !strings.Contains(shell.instanceFeedback, "及其 User Data Dir 数据") {
t.Fatalf("data deletion did not remove target: rows=%#v feedback=%q", shell.rows, shell.instanceFeedback)
}
}
func TestShellKeepsInstanceWhenDataDeletionFails(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[3]
shell.OnDeleteInstanceData(func(context.Context, InstanceRow) error {
return errors.New("permission denied")
}, nil)
shell.requestDelete(target.ID)
shell.deleteData.Value = true
shell.confirmDelete()
var result instanceDataDeleteResult
select {
case result = <-shell.deleteDataResults:
case <-time.After(time.Second):
t.Fatal("data deletion did not produce a result")
}
shell.deleteDataResults <- result
shell.consumeInstanceDataDeleteResults()
if !shell.deleteData.Value || shell.pendingDeleteID != target.ID || shell.deleteDataRunning || len(shell.rows) != 4 {
t.Fatalf("failed deletion state = data=%v pending=%q running=%v rows=%d", shell.deleteData.Value, shell.pendingDeleteID, shell.deleteDataRunning, len(shell.rows))
}
if !strings.Contains(shell.deleteDataFeedback, "未删除实例配置或 User Data Dir") {
t.Fatalf("failure feedback = %q", shell.deleteDataFeedback)
}
}
func TestShellRejectsInstanceDataDeletionWhenStatusIsActive(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[0]
called := false
shell.OnDeleteInstanceData(func(context.Context, InstanceRow) error {
called = true
return nil
}, nil)
shell.requestDelete(target.ID)
shell.deleteData.Value = true
shell.confirmDelete()
if called || shell.deleteDataRunning || len(shell.rows) != 4 {
t.Fatalf("active data deletion called=%v running=%v rows=%d", called, shell.deleteDataRunning, len(shell.rows))
}
if !strings.Contains(shell.deleteDataFeedback, "当前状态不能删除") {
t.Fatalf("active deletion feedback = %q", shell.deleteDataFeedback)
}
}
func TestShellDiscardsStaleInstanceDataDeletionResult(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[3]
shell.OnDeleteInstanceData(func(context.Context, InstanceRow) error { return nil }, nil)
shell.requestDelete(target.ID)
shell.deleteData.Value = true
shell.confirmDelete()
var result instanceDataDeleteResult
select {
case result = <-shell.deleteDataResults:
case <-time.After(time.Second):
t.Fatal("data deletion did not produce a result")
}
shell.rows[3].UserDataDir = "C:\\profiles\\changed"
shell.deleteDataResults <- result
shell.consumeInstanceDataDeleteResults()
if shell.pendingDeleteID != target.ID || len(shell.rows) != 4 || !strings.Contains(shell.deleteDataFeedback, "配置已变化") {
t.Fatalf("stale deletion state = pending=%q rows=%d feedback=%q", shell.pendingDeleteID, len(shell.rows), shell.deleteDataFeedback)
}
}
func TestShellStartsInstanceAsynchronouslyAndAppliesResult(t *testing.T) {
shell := NewShell(material.NewTheme())
target := shell.rows[0]