arblant
firefox-vpn-client / core / internal / service / autoconnect_test.go
autoconnect_test.go127 lines · 4.4 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
package service

import (
	"net/http"
	"sync/atomic"
	"testing"
	"time"

	"github.com/armanlife/firefox-vpn-client/core/internal/config"
)

// fakeAutoConnect replaces auto-connect's test with pass (counting the
// calls) and shortens its waits to window and a millisecond.
func fakeAutoConnect(t *testing.T, window time.Duration, pass func(call int32) bool) *atomic.Int32 {
	t.Helper()
	var calls atomic.Int32
	oldProbe, oldWindow, oldRetry := autoConnectProbe, autoConnectWindow, autoConnectRetry
	autoConnectProbe = func(*Daemon) bool { return pass(calls.Add(1)) }
	autoConnectWindow = window
	autoConnectRetry = func(int) time.Duration { return time.Millisecond }
	t.Cleanup(func() { autoConnectProbe, autoConnectWindow, autoConnectRetry = oldProbe, oldWindow, oldRetry })
	return &calls
}

// runOffDaemon runs the lazy daemon with the proxy off and the system proxy
// mode set to mode, as the desktop app starts it.
func runOffDaemon(t *testing.T, mode string) *Daemon {
	t.Helper()
	d, _ := runLazyDaemon(t)
	d.SystemProxySupported = true
	if err := d.SetSystemProxy(mode); err != nil {
		t.Fatal(err)
	}
	d.SetOn(false)
	waitMessage(t, d, PhaseOff, "Proxy off")
	return d
}

func autoConnecting(t *testing.T, d *Daemon) bool {
	t.Helper()
	view, err := d.StatusView()
	if err != nil {
		t.Fatal(err)
	}
	return view.AutoConnecting
}

// With the system proxy set, the system proxy waits until a test passes;
// the proxy stays on.
func TestAutoConnectHoldsTheSystemProxyUntilATestPasses(t *testing.T) {
	calls := fakeAutoConnect(t, time.Minute, func(call int32) bool { return call >= 3 })
	d := runOffDaemon(t, config.SystemProxySet)
	client := newDashboardClient(t, d)
	if rec := client.do(http.MethodPost, "/api/power", map[string]bool{"on": true, "auto": true}); rec.Code != http.StatusOK {
		t.Fatalf("auto: %d %s", rec.Code, rec.Body)
	}
	waitMessage(t, d, PhaseRunning, "Proxy running")
	waitForCondition(t, 5*time.Second, func() bool { return !autoConnecting(t, d) })
	if n := calls.Load(); n != 3 {
		t.Fatalf("tests %d; want 3, until the first passed", n)
	}
	if d.isOff() {
		t.Fatal("the proxy must stay on after a passed test")
	}
}

// No passed test within the window: the proxy goes off again, the system
// proxy never set.
func TestAutoConnectGivesUpAndTurnsTheProxyOff(t *testing.T) {
	fakeAutoConnect(t, 50*time.Millisecond, func(int32) bool { return false })
	d := runOffDaemon(t, config.SystemProxySet)
	d.AutoConnect()
	if !autoConnecting(t, d) {
		t.Fatal("the system proxy must wait for the test")
	}
	// The phase was off before the attempt too, so wait for the attempt to
	// end first, then for the proxy to be off again.
	waitForCondition(t, 5*time.Second, func() bool { return !autoConnecting(t, d) })
	waitMessage(t, d, PhaseOff, "Proxy off")
	if !d.isOff() {
		t.Fatal("the proxy must be off again when the attempt gives up")
	}
}

// The window counts from when a network connected.
func TestAutoConnectWaitsForTheNetwork(t *testing.T) {
	calls := fakeAutoConnect(t, time.Minute, func(int32) bool { return true })
	d := runOffDaemon(t, config.SystemProxySet)
	online := setNetwork(t)
	d.AutoConnect()
	time.Sleep(50 * time.Millisecond)
	if n := calls.Load(); n != 0 || !autoConnecting(t, d) {
		t.Fatalf("tested %d times without a network", n)
	}
	online.Store(true)
	waitForCondition(t, 5*time.Second, func() bool { return !autoConnecting(t, d) })
}

// The user's own switch ends the attempt; the proxy stays as they set it.
func TestAutoConnectEndsWhenTheUserActs(t *testing.T) {
	fakeAutoConnect(t, time.Minute, func(int32) bool { return false })
	d := runOffDaemon(t, config.SystemProxySet)
	d.AutoConnect()
	waitMessage(t, d, PhaseRunning, "Proxy running")
	if err := d.SetSystemProxy(config.SystemProxyClear); err != nil {
		t.Fatal(err)
	}
	if autoConnecting(t, d) {
		t.Fatal("a mode change must end the attempt")
	}
	time.Sleep(50 * time.Millisecond)
	if d.isOff() {
		t.Fatal("an ended attempt must not turn the proxy off")
	}
}

// Proxy only: auto-connect only turns the proxy on, with no test.
func TestAutoConnectProxyOnly(t *testing.T) {
	calls := fakeAutoConnect(t, time.Minute, func(int32) bool { return false })
	d := runOffDaemon(t, config.SystemProxyClear)
	d.AutoConnect()
	waitMessage(t, d, PhaseRunning, "Proxy running")
	time.Sleep(50 * time.Millisecond)
	if n := calls.Load(); n != 0 || autoConnecting(t, d) {
		t.Fatalf("tests %d, waiting %t; want neither", n, autoConnecting(t, d))
	}
}