feat: support explicit instance data deletion
This commit is contained in:
+105
-2
@@ -12,6 +12,7 @@ import (
|
||||
"chub/internal/domain"
|
||||
"chub/internal/platform/browser"
|
||||
"chub/internal/platform/config"
|
||||
"chub/internal/platform/files"
|
||||
"chub/internal/ui"
|
||||
)
|
||||
|
||||
@@ -276,6 +277,90 @@ func TestInstanceStatusRefresherAssociatesExternalWithoutLifecycleControl(t *tes
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceDataDeleterDeletesOnlyUnoccupiedConfiguredProfile(t *testing.T) {
|
||||
remover := &fakeInstanceDataRemover{}
|
||||
deleter := instanceDataDeleter{
|
||||
managedProfiles: fakeManagedProfileInspector{},
|
||||
externalProfiles: fakeExternalProfileInspector{},
|
||||
dataRemover: remover,
|
||||
}
|
||||
row := ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`}
|
||||
|
||||
if err := deleter.Delete(context.Background(), row); err != nil {
|
||||
t.Fatalf("Delete() error = %v", err)
|
||||
}
|
||||
if remover.calls != 1 || remover.userDataDir != row.UserDataDir {
|
||||
t.Fatalf("remover calls=%d userDataDir=%q, want 1 %q", remover.calls, remover.userDataDir, row.UserDataDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceDataDeleterRejectsOccupiedProfileBeforeRemoval(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
managed fakeManagedProfileInspector
|
||||
external fakeExternalProfileInspector
|
||||
}{
|
||||
{
|
||||
name: "chub managed instance",
|
||||
managed: fakeManagedProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 4242}},
|
||||
},
|
||||
{
|
||||
name: "external browser instance",
|
||||
external: fakeExternalProfileInspector{use: browser.ProfileUse{Occupied: true, PID: 4243, Source: browser.ProfileSourceLock}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
remover := &fakeInstanceDataRemover{}
|
||||
deleter := instanceDataDeleter{
|
||||
managedProfiles: tt.managed,
|
||||
externalProfiles: tt.external,
|
||||
dataRemover: remover,
|
||||
}
|
||||
|
||||
err := deleter.Delete(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`})
|
||||
if !errors.Is(err, domain.ErrProfileOccupied) {
|
||||
t.Fatalf("Delete() error = %v, want profile occupied", err)
|
||||
}
|
||||
if remover.calls != 0 {
|
||||
t.Fatalf("remover calls = %d, want 0", remover.calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceDataDeleterPreservesDataWhenSafetyCheckFails(t *testing.T) {
|
||||
remover := &fakeInstanceDataRemover{}
|
||||
deleter := instanceDataDeleter{
|
||||
managedProfiles: fakeManagedProfileInspector{err: errors.New("registry unavailable")},
|
||||
externalProfiles: fakeExternalProfileInspector{},
|
||||
dataRemover: remover,
|
||||
}
|
||||
|
||||
err := deleter.Delete(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `C:\profiles\chrome-a`})
|
||||
if err == nil || remover.calls != 0 {
|
||||
t.Fatalf("Delete() error = %v, remover calls = %d; want safety failure before removal", err, remover.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceDataDeleterRejectsUnsafeDirectoryBeforeInspection(t *testing.T) {
|
||||
remover := &fakeInstanceDataRemover{}
|
||||
deleter := instanceDataDeleter{
|
||||
managedProfiles: fakeManagedProfileInspector{},
|
||||
externalProfiles: fakeExternalProfileInspector{},
|
||||
dataRemover: remover,
|
||||
}
|
||||
|
||||
err := deleter.Delete(context.Background(), ui.InstanceRow{ID: "chrome-a", Browser: "Chrome", UserDataDir: `profiles\chrome-a`})
|
||||
if !errors.Is(err, files.ErrUnsafeInstanceDataDirectory) {
|
||||
t.Fatalf("Delete() error = %v, want unsafe directory", err)
|
||||
}
|
||||
if remover.calls != 0 {
|
||||
t.Fatalf("remover calls = %d, want 0", remover.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceTabsInspectorReadsOnlyVerifiedRuntimeRows(t *testing.T) {
|
||||
remote := &fakeRemoteDebugInspector{targets: []browser.RemoteDebugTarget{{ID: "page-1", Type: "page", Title: "运营后台", URL: "https://example.com/orders"}}}
|
||||
inspector := instanceTabsInspector{remoteDebug: remote}
|
||||
@@ -373,10 +458,28 @@ func (m *fakeManagedExitObserver) ExpectStop(id string, generation uint64, pid i
|
||||
|
||||
func (m *fakeManagedExitObserver) ClearExpectedStop(string, uint64, int) { m.clearCalls++ }
|
||||
|
||||
type fakeManagedProfileInspector struct{ use browser.ProfileUse }
|
||||
type fakeManagedProfileInspector struct {
|
||||
use browser.ProfileUse
|
||||
err error
|
||||
}
|
||||
|
||||
func (i fakeManagedProfileInspector) InspectProfile(context.Context, string) (browser.ProfileUse, error) {
|
||||
return i.use, nil
|
||||
return i.use, i.err
|
||||
}
|
||||
|
||||
type fakeInstanceDataRemover struct {
|
||||
calls int
|
||||
userDataDir string
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *fakeInstanceDataRemover) RemoveInstanceData(ctx context.Context, userDataDir string) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
r.calls++
|
||||
r.userDataDir = userDataDir
|
||||
return r.err
|
||||
}
|
||||
|
||||
type fakeManagedStopper struct {
|
||||
|
||||
Reference in New Issue
Block a user