Files
gopeed/pkg/download/extension_webview_remove_test.go
T

190 lines
5.8 KiB
Go

package download
import (
"errors"
"os"
"path/filepath"
"sync"
"testing"
"github.com/GopeedLab/gopeed/pkg/base"
webview "github.com/GopeedLab/gopeed/pkg/download/engine/webview"
)
type removalProvider struct {
pages map[string][]*removalPage
removed []string
failure error
entered chan struct{}
resume chan struct{}
}
type removalPage struct {
fakeRuntimeWebViewPage
closed bool
}
func (p *removalPage) Close() error { p.closed = true; return nil }
func (p *removalProvider) IsAvailable() bool { return true }
func (p *removalProvider) Open(opts webview.OpenOptions) (webview.Page, error) {
if p.entered != nil {
close(p.entered)
<-p.resume
}
if p.pages == nil {
p.pages = make(map[string][]*removalPage)
}
page := &removalPage{}
p.pages[opts.ProfileID] = append(p.pages[opts.ProfileID], page)
return page, nil
}
func (p *removalProvider) RemoveProfile(id, path string) error {
for _, page := range p.pages[id] {
if !page.closed {
return errors.New("profile removed before closing pages")
}
}
if p.failure != nil {
return p.failure
}
p.removed = append(p.removed, id)
return nil
}
func removalDownloader(t *testing.T, provider *removalProvider) *Downloader {
t.Helper()
storage := NewMemStorage()
if err := storage.Setup([]string{bucketExtension, bucketExtensionStorage}); err != nil {
t.Fatal(err)
}
d := &Downloader{cfg: &DownloaderConfig{StorageDir: t.TempDir(), DownloaderStoreConfig: &base.DownloaderStoreConfig{}, WebViewProvider: provider}, storage: storage, configLock: &sync.RWMutex{}}
t.Cleanup(func() {
if d.webviewProxy != nil {
d.webviewProxy.Close()
}
})
return d
}
func profileSentinel(t *testing.T, d *Downloader, id string) string {
t.Helper()
_, path, err := d.extensionWebViewLocation(id)
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path, 0700); err != nil {
t.Fatal(err)
}
sentinel := filepath.Join(path, "cookies.sqlite")
if err := os.WriteFile(sentinel, []byte("private browser state"), 0600); err != nil {
t.Fatal(err)
}
return sentinel
}
func TestDeleteExtensionRemovesWebViewData(t *testing.T) {
provider := &removalProvider{}
d := removalDownloader(t, provider)
ext, err := d.InstallExtensionByFolder("testdata/extensions/basic", true)
if err != nil {
t.Fatal(err)
}
identity := ext.Identity
opener := &extensionWebViewOpener{downloader: d, provider: provider, identity: identity, profile: d.extensionWebViewProfile(identity)}
for i := 0; i < 2; i++ {
if _, err := opener.Open(webview.OpenOptions{}); err != nil {
t.Fatal(err)
}
}
sentinel := profileSentinel(t, d, identity)
other := profileSentinel(t, d, "other-extension")
// Updating the installation keeps both its profile object and stored data.
if _, err := d.InstallExtensionByFolder("testdata/extensions/basic", true); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(sentinel); err != nil {
t.Fatal("upgrade removed browser data", err)
}
if err := d.DeleteExtension(identity); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(sentinel); !os.IsNotExist(err) {
t.Fatalf("browser data retained: %v", err)
}
if _, err := os.Stat(other); err != nil {
t.Fatal("other extension data was removed", err)
}
if _, err := os.Stat(ext.DevPath); err != nil {
t.Fatal("dev source was removed", err)
}
if len(provider.removed) != 1 {
t.Fatal("native profile was not removed")
}
if _, err := opener.Open(webview.OpenOptions{}); err == nil {
t.Fatal("old runtime recreated removed profile")
}
if _, err := d.InstallExtensionByFolder("testdata/extensions/basic", true); err != nil {
t.Fatal(err)
}
fresh := &extensionWebViewOpener{downloader: d, provider: provider, identity: identity, profile: d.extensionWebViewProfile(identity)}
if _, err := fresh.Open(webview.OpenOptions{}); err != nil {
t.Fatal("reinstall could not open profile", err)
}
if _, err := opener.Open(webview.OpenOptions{}); err == nil {
t.Fatal("reinstall reactivated old runtime")
}
}
func TestDeleteExtensionProfileFailureIsRetryable(t *testing.T) {
provider := &removalProvider{failure: errors.New("native store busy")}
d := removalDownloader(t, provider)
ext, err := d.InstallExtensionByFolder("testdata/extensions/basic", true)
if err != nil {
t.Fatal(err)
}
sentinel := profileSentinel(t, d, ext.Identity)
if err := d.DeleteExtension(ext.Identity); err == nil {
t.Fatal("cleanup failure was swallowed")
}
if _, err := d.GetExtension(ext.Identity); err != nil {
t.Fatal("extension removed before cleanup completed")
}
if _, err := os.Stat(sentinel); err != nil {
t.Fatal("profile path removed before native cleanup completed")
}
provider.failure = nil
if err := d.DeleteExtension(ext.Identity); err != nil {
t.Fatal(err)
}
if _, err := d.GetExtension(ext.Identity); !errors.Is(err, ErrExtensionNotFound) {
t.Fatal("extension was not uninstalled")
}
}
func TestRemoveProfileWaitsForOpeningPage(t *testing.T) {
provider := &removalProvider{entered: make(chan struct{}), resume: make(chan struct{})}
d := removalDownloader(t, provider)
opener := &extensionWebViewOpener{downloader: d, provider: provider, identity: "race", profile: d.extensionWebViewProfile("race")}
opened := make(chan error, 1)
go func() { _, err := opener.Open(webview.OpenOptions{}); opened <- err }()
<-provider.entered
removed := make(chan error, 1)
go func() { removed <- d.removeExtensionWebViewProfile("race") }()
close(provider.resume)
if err := <-opened; err != nil {
t.Fatal(err)
}
if err := <-removed; err != nil {
t.Fatal(err)
}
if len(provider.removed) != 1 {
t.Fatal("profile not removed")
}
}
func TestUninstallWithoutAvailableWebView(t *testing.T) {
d := removalDownloader(t, &removalProvider{})
d.cfg.WebViewProvider = webview.NewUnavailableProvider()
ext, err := d.InstallExtensionByFolder("testdata/extensions/basic", true)
if err != nil {
t.Fatal(err)
}
if err := d.DeleteExtension(ext.Identity); err != nil {
t.Fatal(err)
}
}