mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			371 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			371 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package util
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"net/netip"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/google/go-cmp/cmp"
 | 
						|
)
 | 
						|
 | 
						|
func TestTailscaleVersionNewerOrEqual(t *testing.T) {
 | 
						|
	type args struct {
 | 
						|
		minimum string
 | 
						|
		toCheck string
 | 
						|
	}
 | 
						|
	tests := []struct {
 | 
						|
		name string
 | 
						|
		args args
 | 
						|
		want bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "is-equal",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "1.56",
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-newer-head",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "head",
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-newer-unstable",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "unstable",
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-newer-patch",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56.1",
 | 
						|
				toCheck: "1.56.1",
 | 
						|
			},
 | 
						|
			want: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-older-patch-same-minor",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56.1",
 | 
						|
				toCheck: "1.56.0",
 | 
						|
			},
 | 
						|
			want: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-older-unstable",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "1.55",
 | 
						|
			},
 | 
						|
			want: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-older-one-stable",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "1.54",
 | 
						|
			},
 | 
						|
			want: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-older-five-stable",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "1.46",
 | 
						|
			},
 | 
						|
			want: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "is-older-patch",
 | 
						|
			args: args{
 | 
						|
				minimum: "1.56",
 | 
						|
				toCheck: "1.48.1",
 | 
						|
			},
 | 
						|
			want: false,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			if got := TailscaleVersionNewerOrEqual(tt.args.minimum, tt.args.toCheck); got != tt.want {
 | 
						|
				t.Errorf("TailscaleVersionNewerThan() = %v, want %v", got, tt.want)
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestParseLoginURLFromCLILogin(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name    string
 | 
						|
		output  string
 | 
						|
		wantURL string
 | 
						|
		wantErr string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "valid https URL",
 | 
						|
			output: `
 | 
						|
To authenticate, visit:
 | 
						|
 | 
						|
        https://headscale.example.com/register/3oYCOZYA2zZmGB4PQ7aHBaMi
 | 
						|
 | 
						|
Success.`,
 | 
						|
			wantURL: "https://headscale.example.com/register/3oYCOZYA2zZmGB4PQ7aHBaMi",
 | 
						|
			wantErr: "",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "valid http URL",
 | 
						|
			output: `
 | 
						|
To authenticate, visit:
 | 
						|
 | 
						|
        http://headscale.example.com/register/3oYCOZYA2zZmGB4PQ7aHBaMi
 | 
						|
 | 
						|
Success.`,
 | 
						|
			wantURL: "http://headscale.example.com/register/3oYCOZYA2zZmGB4PQ7aHBaMi",
 | 
						|
			wantErr: "",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "no URL",
 | 
						|
			output: `
 | 
						|
To authenticate, visit:
 | 
						|
 | 
						|
Success.`,
 | 
						|
			wantURL: "",
 | 
						|
			wantErr: "no URL found",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "multiple URLs",
 | 
						|
			output: `
 | 
						|
To authenticate, visit:
 | 
						|
 | 
						|
        https://headscale.example.com/register/3oYCOZYA2zZmGB4PQ7aHBaMi
 | 
						|
 | 
						|
To authenticate, visit:
 | 
						|
 | 
						|
        http://headscale.example.com/register/dv1l2k5FackOYl-7-V3mSd_E
 | 
						|
 | 
						|
Success.`,
 | 
						|
			wantURL: "",
 | 
						|
			wantErr: "multiple URLs found: https://headscale.example.com/register/3oYCOZYA2zZmGB4PQ7aHBaMi and http://headscale.example.com/register/dv1l2k5FackOYl-7-V3mSd_E",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "invalid URL",
 | 
						|
			output: `
 | 
						|
To authenticate, visit:
 | 
						|
 | 
						|
        invalid-url
 | 
						|
 | 
						|
Success.`,
 | 
						|
			wantURL: "",
 | 
						|
			wantErr: "no URL found",
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			gotURL, err := ParseLoginURLFromCLILogin(tt.output)
 | 
						|
			if tt.wantErr != "" {
 | 
						|
				if err == nil || err.Error() != tt.wantErr {
 | 
						|
					t.Errorf("ParseLoginURLFromCLILogin() error = %v, wantErr %v", err, tt.wantErr)
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if err != nil {
 | 
						|
					t.Errorf("ParseLoginURLFromCLILogin() error = %v, wantErr %v", err, tt.wantErr)
 | 
						|
				}
 | 
						|
				if gotURL.String() != tt.wantURL {
 | 
						|
					t.Errorf("ParseLoginURLFromCLILogin() = %v, want %v", gotURL, tt.wantURL)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestParseTraceroute(t *testing.T) {
 | 
						|
	tests := []struct {
 | 
						|
		name    string
 | 
						|
		input   string
 | 
						|
		want    Traceroute
 | 
						|
		wantErr bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name: "simple successful traceroute",
 | 
						|
			input: `traceroute to 172.24.0.3 (172.24.0.3), 30 hops max, 46 byte packets
 | 
						|
 1  ts-head-hk0urr.headscale.net (100.64.0.1)  1.135 ms  0.922 ms  0.619 ms
 | 
						|
 2  172.24.0.3 (172.24.0.3)  0.593 ms  0.549 ms  0.522 ms`,
 | 
						|
			want: Traceroute{
 | 
						|
				Hostname: "172.24.0.3",
 | 
						|
				IP:       netip.MustParseAddr("172.24.0.3"),
 | 
						|
				Route: []TraceroutePath{
 | 
						|
					{
 | 
						|
						Hop:      1,
 | 
						|
						Hostname: "ts-head-hk0urr.headscale.net",
 | 
						|
						IP:       netip.MustParseAddr("100.64.0.1"),
 | 
						|
						Latencies: []time.Duration{
 | 
						|
							1135 * time.Microsecond,
 | 
						|
							922 * time.Microsecond,
 | 
						|
							619 * time.Microsecond,
 | 
						|
						},
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      2,
 | 
						|
						Hostname: "172.24.0.3",
 | 
						|
						IP:       netip.MustParseAddr("172.24.0.3"),
 | 
						|
						Latencies: []time.Duration{
 | 
						|
							593 * time.Microsecond,
 | 
						|
							549 * time.Microsecond,
 | 
						|
							522 * time.Microsecond,
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Success: true,
 | 
						|
				Err:     nil,
 | 
						|
			},
 | 
						|
			wantErr: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "traceroute with timeouts",
 | 
						|
			input: `traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 | 
						|
 1  router.local (192.168.1.1)  1.234 ms  1.123 ms  1.121 ms
 | 
						|
 2  * * *
 | 
						|
 3  isp-gateway.net (10.0.0.1)  15.678 ms  14.789 ms  15.432 ms
 | 
						|
 4  8.8.8.8 (8.8.8.8)  20.123 ms  19.876 ms  20.345 ms`,
 | 
						|
			want: Traceroute{
 | 
						|
				Hostname: "8.8.8.8",
 | 
						|
				IP:       netip.MustParseAddr("8.8.8.8"),
 | 
						|
				Route: []TraceroutePath{
 | 
						|
					{
 | 
						|
						Hop:      1,
 | 
						|
						Hostname: "router.local",
 | 
						|
						IP:       netip.MustParseAddr("192.168.1.1"),
 | 
						|
						Latencies: []time.Duration{
 | 
						|
							1234 * time.Microsecond,
 | 
						|
							1123 * time.Microsecond,
 | 
						|
							1121 * time.Microsecond,
 | 
						|
						},
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      2,
 | 
						|
						Hostname: "*",
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      3,
 | 
						|
						Hostname: "isp-gateway.net",
 | 
						|
						IP:       netip.MustParseAddr("10.0.0.1"),
 | 
						|
						Latencies: []time.Duration{
 | 
						|
							15678 * time.Microsecond,
 | 
						|
							14789 * time.Microsecond,
 | 
						|
							15432 * time.Microsecond,
 | 
						|
						},
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      4,
 | 
						|
						Hostname: "8.8.8.8",
 | 
						|
						IP:       netip.MustParseAddr("8.8.8.8"),
 | 
						|
						Latencies: []time.Duration{
 | 
						|
							20123 * time.Microsecond,
 | 
						|
							19876 * time.Microsecond,
 | 
						|
							20345 * time.Microsecond,
 | 
						|
						},
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Success: true,
 | 
						|
				Err:     nil,
 | 
						|
			},
 | 
						|
			wantErr: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name: "unsuccessful traceroute",
 | 
						|
			input: `traceroute to 10.0.0.99 (10.0.0.99), 5 hops max, 60 byte packets
 | 
						|
 1  router.local (192.168.1.1)  1.234 ms  1.123 ms  1.121 ms
 | 
						|
 2  * * *
 | 
						|
 3  * * *
 | 
						|
 4  * * *
 | 
						|
 5  * * *`,
 | 
						|
			want: Traceroute{
 | 
						|
				Hostname: "10.0.0.99",
 | 
						|
				IP:       netip.MustParseAddr("10.0.0.99"),
 | 
						|
				Route: []TraceroutePath{
 | 
						|
					{
 | 
						|
						Hop:      1,
 | 
						|
						Hostname: "router.local",
 | 
						|
						IP:       netip.MustParseAddr("192.168.1.1"),
 | 
						|
						Latencies: []time.Duration{
 | 
						|
							1234 * time.Microsecond,
 | 
						|
							1123 * time.Microsecond,
 | 
						|
							1121 * time.Microsecond,
 | 
						|
						},
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      2,
 | 
						|
						Hostname: "*",
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      3,
 | 
						|
						Hostname: "*",
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      4,
 | 
						|
						Hostname: "*",
 | 
						|
					},
 | 
						|
					{
 | 
						|
						Hop:      5,
 | 
						|
						Hostname: "*",
 | 
						|
					},
 | 
						|
				},
 | 
						|
				Success: false,
 | 
						|
				Err:     errors.New("traceroute did not reach target"),
 | 
						|
			},
 | 
						|
			wantErr: false,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:    "empty input",
 | 
						|
			input:   "",
 | 
						|
			want:    Traceroute{},
 | 
						|
			wantErr: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:    "invalid header",
 | 
						|
			input:   "not a valid traceroute output",
 | 
						|
			want:    Traceroute{},
 | 
						|
			wantErr: true,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, tt := range tests {
 | 
						|
		t.Run(tt.name, func(t *testing.T) {
 | 
						|
			got, err := ParseTraceroute(tt.input)
 | 
						|
			if (err != nil) != tt.wantErr {
 | 
						|
				t.Errorf("ParseTraceroute() error = %v, wantErr %v", err, tt.wantErr)
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			if tt.wantErr {
 | 
						|
				return
 | 
						|
			}
 | 
						|
 | 
						|
			// Special handling for error field since it can't be directly compared with cmp.Diff
 | 
						|
			gotErr := got.Err
 | 
						|
			wantErr := tt.want.Err
 | 
						|
			got.Err = nil
 | 
						|
			tt.want.Err = nil
 | 
						|
 | 
						|
			if diff := cmp.Diff(tt.want, got, IPComparer); diff != "" {
 | 
						|
				t.Errorf("ParseTraceroute() mismatch (-want +got):\n%s", diff)
 | 
						|
			}
 | 
						|
 | 
						|
			// Now check error field separately
 | 
						|
			if (gotErr == nil) != (wantErr == nil) {
 | 
						|
				t.Errorf("Error field: got %v, want %v", gotErr, wantErr)
 | 
						|
			} else if gotErr != nil && wantErr != nil && gotErr.Error() != wantErr.Error() {
 | 
						|
				t.Errorf("Error message: got %q, want %q", gotErr.Error(), wantErr.Error())
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |