arblant
firefox-vpn-client / core / internal / service / client_test.go
client_test.go108 lines · 4.0 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
package service

import (
	"context"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
	"time"

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

func TestLocalClientReadsStatusThroughToken(t *testing.T) {
	d := newTestDaemon(t)
	d.localToken = "client-token"
	server := httptest.NewServer(newDashboardServer(d).handler())
	defer server.Close()

	cfg := d.config()
	cfg.Dashboard.Listen = strings.TrimPrefix(server.URL, "http://")
	if err := config.Save(d.paths.Config, cfg); err != nil {
		t.Fatal(err)
	}
	client := NewClient(d.paths)
	ctx := context.Background()
	if _, err := client.Status(ctx); err == nil || !strings.Contains(err.Error(), "not running") {
		t.Fatalf("without a token file: got %v", err)
	}
	if err := os.WriteFile(d.paths.LocalToken, []byte(d.localToken), 0o600); err != nil {
		t.Fatal(err)
	}
	d.setState(PhaseWaiting, "Add an account", time.Time{})
	st, err := client.Status(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if got := st.Summary(); got != "Waiting: Add an account" {
		t.Fatalf("summary = %q", got)
	}
	st.State.Phase = "running"
	st.Proxy = &TrayProxy{CountryName: "Belgium", CityName: "Brussels"}
	if got := st.Summary(); got != "Connected · Belgium" {
		t.Fatalf("summary = %q", got)
	}
	// The recommended location names no place; the public IP does.
	st.Proxy.CountryCode, st.Proxy.CountryName = "REC", "Recommended location"
	if got := st.Summary(); got != "Connected" {
		t.Fatalf("recommended without a public IP: %q", got)
	}
	st.Exit = &struct {
		CountryCode string `json:"country_code"`
		CountryName string `json:"country_name"`
		City        string `json:"city"`
	}{CountryCode: "NL", CountryName: "Netherlands", City: "Amsterdam"}
	if got := st.Summary(); got != "Connected · Amsterdam, Netherlands" {
		t.Fatalf("with the public IP: %q", got)
	}
	st.Proxy.SleepingSince = time.Now()
	if got := st.Summary(); got != "Sleeping (wakes on the next connection) · Amsterdam, Netherlands" {
		t.Fatalf("sleeping: %q", got)
	}
	if err := client.RestartProxy(ctx); err != nil {
		t.Fatalf("restart: %v", err)
	}
	if _, err := client.ConnectionTest(ctx); err == nil || !strings.Contains(err.Error(), "not running") {
		t.Fatalf("connection test without engine: got %v", err)
	}
}

func TestLoopbackAddr(t *testing.T) {
	for in, want := range map[string]string{
		"127.0.0.1:10820":  "127.0.0.1:10820",
		"0.0.0.0:10820":    "127.0.0.1:10820",
		"[::]:10820":       "127.0.0.1:10820",
		":10820":           "127.0.0.1:10820",
		"192.168.1.5:8080": "192.168.1.5:8080",
		"localhost:10820":  "localhost:10820",
		"[::1]:10820":      "[::1]:10820",
		"myhost.lan:10820": "127.0.0.1:10820",
	} {
		if got := loopbackAddr(in); got != want {
			t.Errorf("loopbackAddr(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestSummaryNamesTheStateAndReason(t *testing.T) {
	long := "listen tcp 127.0.0.1:10810: bind: Only one usage of each socket address (protocol/network address/port) is normally permitted"
	for _, tc := range []struct{ phase, code, message, want string }{
		{PhaseOff, "", "Proxy off", "Proxy off"},
		{PhaseStarting, "", "Starting", "Starting…"},
		{PhaseWaiting, CodeWaitingForNetwork, MessageWaitingForNetwork, MessageWaitingForNetwork},
		{PhaseWaiting, CodeNoAccounts, "Add an account in the dashboard to start the proxy", "Waiting: Add an account"},
		{PhaseWaiting, CodeNoUsableAccount, "No usable account: a@example.com: needs sign-in", "Waiting: No usable account"},
		{PhaseError, CodeProxyPortInUse, "address in use. Choose another SOCKS5 listen address in Settings", "Error: The proxy's port is in use"},
		{PhaseError, CodeStartFailed, "Guardian refused the pass. Sign in again", "Error: Guardian refused the pass"},
		{PhaseError, CodeStartFailed, long, "Error: " + string([]rune(long)[:summaryReasonRunes-1]) + "…"},
		{PhaseError, "", "", "Error"},
		{"", "", "", "Unknown state"},
	} {
		var st TrayStatus
		st.State.Phase, st.State.Code, st.State.Message = tc.phase, tc.code, tc.message
		if got := st.Summary(); got != tc.want {
			t.Errorf("%s/%s: summary = %q, want %q", tc.phase, tc.code, got, tc.want)
		}
	}
}