| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package clientupdate
- import (
- "archive/tar"
- "compress/gzip"
- "fmt"
- "io/fs"
- "maps"
- "os"
- "path/filepath"
- "slices"
- "sort"
- "strings"
- "testing"
- )
- func TestUpdateDebianAptSourcesListBytes(t *testing.T) {
- tests := []struct {
- name string
- toTrack string
- in string
- want string // empty means want no change
- wantErr string
- }{
- {
- name: "stable-to-unstable",
- toTrack: UnstableTrack,
- in: "# Tailscale packages for debian buster\ndeb https://pkgs.tailscale.com/stable/debian bullseye main\n",
- want: "# Tailscale packages for debian buster\ndeb https://pkgs.tailscale.com/unstable/debian bullseye main\n",
- },
- {
- name: "stable-unchanged",
- toTrack: StableTrack,
- in: "# Tailscale packages for debian buster\ndeb https://pkgs.tailscale.com/stable/debian bullseye main\n",
- },
- {
- name: "if-both-stable-and-unstable-dont-change",
- toTrack: StableTrack,
- in: "# Tailscale packages for debian buster\n" +
- "deb https://pkgs.tailscale.com/stable/debian bullseye main\n" +
- "deb https://pkgs.tailscale.com/unstable/debian bullseye main\n",
- },
- {
- name: "if-both-stable-and-unstable-dont-change-unstable",
- toTrack: UnstableTrack,
- in: "# Tailscale packages for debian buster\n" +
- "deb https://pkgs.tailscale.com/stable/debian bullseye main\n" +
- "deb https://pkgs.tailscale.com/unstable/debian bullseye main\n",
- },
- {
- name: "signed-by-form",
- toTrack: UnstableTrack,
- in: "# Tailscale packages for ubuntu jammy\ndeb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/ubuntu jammy main\n",
- want: "# Tailscale packages for ubuntu jammy\ndeb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/unstable/ubuntu jammy main\n",
- },
- {
- name: "unsupported-lines",
- toTrack: UnstableTrack,
- in: "# Tailscale packages for ubuntu jammy\ndeb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/foobar/ubuntu jammy main\n",
- wantErr: "unexpected/unsupported /etc/apt/sources.list.d/tailscale.list contents",
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- newContent, err := updateDebianAptSourcesListBytes([]byte(tt.in), tt.toTrack)
- if err != nil {
- if err.Error() != tt.wantErr {
- t.Fatalf("error = %v; want %q", err, tt.wantErr)
- }
- return
- }
- if tt.wantErr != "" {
- t.Fatalf("got no error; want %q", tt.wantErr)
- }
- var gotChange string
- if string(newContent) != tt.in {
- gotChange = string(newContent)
- }
- if gotChange != tt.want {
- t.Errorf("wrong result\n got: %q\nwant: %q", gotChange, tt.want)
- }
- })
- }
- }
- func TestUpdateYUMRepoTrack(t *testing.T) {
- tests := []struct {
- desc string
- before string
- track string
- after string
- rewrote bool
- wantErr bool
- }{
- {
- desc: "same track",
- before: `
- [tailscale-stable]
- name=Tailscale stable
- baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch
- enabled=1
- type=rpm
- repo_gpgcheck=1
- gpgcheck=0
- gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg
- `,
- track: StableTrack,
- after: `
- [tailscale-stable]
- name=Tailscale stable
- baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch
- enabled=1
- type=rpm
- repo_gpgcheck=1
- gpgcheck=0
- gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg
- `,
- },
- {
- desc: "change track",
- before: `
- [tailscale-stable]
- name=Tailscale stable
- baseurl=https://pkgs.tailscale.com/stable/fedora/$basearch
- enabled=1
- type=rpm
- repo_gpgcheck=1
- gpgcheck=0
- gpgkey=https://pkgs.tailscale.com/stable/fedora/repo.gpg
- `,
- track: UnstableTrack,
- after: `
- [tailscale-unstable]
- name=Tailscale unstable
- baseurl=https://pkgs.tailscale.com/unstable/fedora/$basearch
- enabled=1
- type=rpm
- repo_gpgcheck=1
- gpgcheck=0
- gpgkey=https://pkgs.tailscale.com/unstable/fedora/repo.gpg
- `,
- rewrote: true,
- },
- {
- desc: "non-tailscale repo file",
- before: `
- [fedora]
- name=Fedora $releasever - $basearch
- #baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/
- metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch
- enabled=1
- countme=1
- metadata_expire=7d
- repo_gpgcheck=0
- type=rpm
- gpgcheck=1
- gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
- skip_if_unavailable=False
- `,
- track: StableTrack,
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- path := filepath.Join(t.TempDir(), "tailscale.repo")
- if err := os.WriteFile(path, []byte(tt.before), 0644); err != nil {
- t.Fatal(err)
- }
- rewrote, err := updateYUMRepoTrack(path, tt.track)
- if err == nil && tt.wantErr {
- t.Fatal("got nil error, want non-nil")
- }
- if err != nil && !tt.wantErr {
- t.Fatalf("got error %q, want nil", err)
- }
- if err != nil {
- return
- }
- if rewrote != tt.rewrote {
- t.Errorf("got rewrote flag %v, want %v", rewrote, tt.rewrote)
- }
- after, err := os.ReadFile(path)
- if err != nil {
- t.Fatal(err)
- }
- if string(after) != tt.after {
- t.Errorf("got repo file after update:\n%swant:\n%s", after, tt.after)
- }
- })
- }
- }
- func TestParseAlpinePackageVersion(t *testing.T) {
- tests := []struct {
- desc string
- out string
- want string
- wantErr bool
- }{
- {
- desc: "valid version",
- out: `
- tailscale-1.44.2-r0 description:
- The easiest, most secure way to use WireGuard and 2FA
- tailscale-1.44.2-r0 webpage:
- https://tailscale.com/
- tailscale-1.44.2-r0 installed size:
- 32 MiB
- `,
- want: "1.44.2",
- },
- {
- desc: "wrong package output",
- out: `
- busybox-1.36.1-r0 description:
- Size optimized toolbox of many common UNIX utilities
- busybox-1.36.1-r0 webpage:
- https://busybox.net/
- busybox-1.36.1-r0 installed size:
- 924 KiB
- `,
- wantErr: true,
- },
- {
- desc: "missing version",
- out: `
- tailscale description:
- The easiest, most secure way to use WireGuard and 2FA
- tailscale webpage:
- https://tailscale.com/
- tailscale installed size:
- 32 MiB
- `,
- wantErr: true,
- },
- {
- desc: "empty output",
- out: "",
- wantErr: true,
- },
- {
- desc: "multiple versions",
- out: `
- tailscale-1.54.1-r0 description:
- The easiest, most secure way to use WireGuard and 2FA
- tailscale-1.54.1-r0 webpage:
- https://tailscale.com/
- tailscale-1.54.1-r0 installed size:
- 34 MiB
- tailscale-1.58.2-r0 description:
- The easiest, most secure way to use WireGuard and 2FA
- tailscale-1.58.2-r0 webpage:
- https://tailscale.com/
- tailscale-1.58.2-r0 installed size:
- 35 MiB
- `,
- want: "1.58.2",
- },
- }
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- got, err := parseAlpinePackageVersion([]byte(tt.out))
- if err == nil && tt.wantErr {
- t.Fatalf("got nil error and version %q, want non-nil error", got)
- }
- if err != nil && !tt.wantErr {
- t.Fatalf("got error: %q, want nil", err)
- }
- if got != tt.want {
- t.Fatalf("got version: %q, want %q", got, tt.want)
- }
- })
- }
- }
- func TestSynoArch(t *testing.T) {
- tests := []struct {
- goarch string
- synoinfoUnique string
- want string
- wantErr bool
- }{
- {goarch: "amd64", synoinfoUnique: "synology_x86_224", want: "x86_64"},
- {goarch: "arm64", synoinfoUnique: "synology_armv8_124", want: "armv8"},
- {goarch: "386", synoinfoUnique: "synology_i686_415play", want: "i686"},
- {goarch: "arm", synoinfoUnique: "synology_88f6281_213air", want: "88f6281"},
- {goarch: "arm", synoinfoUnique: "synology_88f6282_413j", want: "88f6282"},
- {goarch: "arm", synoinfoUnique: "synology_hi3535_NVR1218", want: "hi3535"},
- {goarch: "arm", synoinfoUnique: "synology_alpine_1517", want: "alpine"},
- {goarch: "arm", synoinfoUnique: "synology_armada370_216se", want: "armada370"},
- {goarch: "arm", synoinfoUnique: "synology_armada375_115", want: "armada375"},
- {goarch: "arm", synoinfoUnique: "synology_armada38x_419slim", want: "armada38x"},
- {goarch: "arm", synoinfoUnique: "synology_armadaxp_RS815", want: "armadaxp"},
- {goarch: "arm", synoinfoUnique: "synology_comcerto2k_414j", want: "comcerto2k"},
- {goarch: "arm", synoinfoUnique: "synology_monaco_216play", want: "monaco"},
- {goarch: "ppc64", synoinfoUnique: "synology_qoriq_413", wantErr: true},
- }
- for _, tt := range tests {
- t.Run(fmt.Sprintf("%s-%s", tt.goarch, tt.synoinfoUnique), func(t *testing.T) {
- synoinfoConfPath := filepath.Join(t.TempDir(), "synoinfo.conf")
- if err := os.WriteFile(
- synoinfoConfPath,
- []byte(fmt.Sprintf("unique=%q\n", tt.synoinfoUnique)),
- 0600,
- ); err != nil {
- t.Fatal(err)
- }
- got, err := synoArch(tt.goarch, synoinfoConfPath)
- if err != nil {
- if !tt.wantErr {
- t.Fatalf("got unexpected error %v", err)
- }
- return
- }
- if tt.wantErr {
- t.Fatalf("got %q, expected an error", got)
- }
- if got != tt.want {
- t.Errorf("got %q, want %q", got, tt.want)
- }
- })
- }
- }
- func TestParseSynoinfo(t *testing.T) {
- tests := []struct {
- desc string
- content string
- want string
- wantErr bool
- }{
- {
- desc: "double-quoted",
- content: `
- company_title="Synology"
- unique="synology_88f6281_213air"
- `,
- want: "88f6281",
- },
- {
- desc: "single-quoted",
- content: `
- company_title="Synology"
- unique='synology_88f6281_213air'
- `,
- want: "88f6281",
- },
- {
- desc: "unquoted",
- content: `
- company_title="Synology"
- unique=synology_88f6281_213air
- `,
- want: "88f6281",
- },
- {
- desc: "missing unique",
- content: `
- company_title="Synology"
- `,
- wantErr: true,
- },
- {
- desc: "empty unique",
- content: `
- company_title="Synology"
- unique=
- `,
- wantErr: true,
- },
- {
- desc: "empty unique double-quoted",
- content: `
- company_title="Synology"
- unique=""
- `,
- wantErr: true,
- },
- {
- desc: "empty unique single-quoted",
- content: `
- company_title="Synology"
- unique=''
- `,
- wantErr: true,
- },
- {
- desc: "malformed unique",
- content: `
- company_title="Synology"
- unique="synology_88f6281"
- `,
- wantErr: true,
- },
- {
- desc: "empty file",
- content: ``,
- wantErr: true,
- },
- {
- desc: "empty lines and comments",
- content: `
- # In a file named synoinfo? Shocking!
- company_title="Synology"
- # unique= is_a_field_that_follows
- unique="synology_88f6281_213air"
- `,
- want: "88f6281",
- },
- }
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- synoinfoConfPath := filepath.Join(t.TempDir(), "synoinfo.conf")
- if err := os.WriteFile(synoinfoConfPath, []byte(tt.content), 0600); err != nil {
- t.Fatal(err)
- }
- got, err := parseSynoinfo(synoinfoConfPath)
- if err != nil {
- if !tt.wantErr {
- t.Fatalf("got unexpected error %v", err)
- }
- return
- }
- if tt.wantErr {
- t.Fatalf("got %q, expected an error", got)
- }
- if got != tt.want {
- t.Errorf("got %q, want %q", got, tt.want)
- }
- })
- }
- }
- func TestUnpackLinuxTarball(t *testing.T) {
- oldBinaryPaths := binaryPaths
- t.Cleanup(func() { binaryPaths = oldBinaryPaths })
- tests := []struct {
- desc string
- tarball map[string]string
- before map[string]string
- after map[string]string
- wantErr bool
- }{
- {
- desc: "success",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- tarball: map[string]string{
- "/usr/bin/tailscale": "v2",
- "/usr/bin/tailscaled": "v2",
- },
- after: map[string]string{
- "tailscale": "v2",
- "tailscaled": "v2",
- },
- },
- {
- desc: "don't touch unrelated files",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- "foo": "bar",
- },
- tarball: map[string]string{
- "/usr/bin/tailscale": "v2",
- "/usr/bin/tailscaled": "v2",
- },
- after: map[string]string{
- "tailscale": "v2",
- "tailscaled": "v2",
- "foo": "bar",
- },
- },
- {
- desc: "unmodified",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- tarball: map[string]string{
- "/usr/bin/tailscale": "v1",
- "/usr/bin/tailscaled": "v1",
- },
- after: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- },
- {
- desc: "ignore extra tarball files",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- tarball: map[string]string{
- "/usr/bin/tailscale": "v2",
- "/usr/bin/tailscaled": "v2",
- "/systemd/tailscaled.service": "v2",
- },
- after: map[string]string{
- "tailscale": "v2",
- "tailscaled": "v2",
- },
- },
- {
- desc: "tarball missing tailscaled",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- tarball: map[string]string{
- "/usr/bin/tailscale": "v2",
- },
- after: map[string]string{
- "tailscale": "v1",
- "tailscale.new": "v2",
- "tailscaled": "v1",
- },
- wantErr: true,
- },
- {
- desc: "duplicate tailscale binary",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- tarball: map[string]string{
- "/usr/bin/tailscale": "v2",
- "/usr/sbin/tailscale": "v2",
- "/usr/bin/tailscaled": "v2",
- },
- after: map[string]string{
- "tailscale": "v1",
- "tailscale.new": "v2",
- "tailscaled": "v1",
- "tailscaled.new": "v2",
- },
- wantErr: true,
- },
- {
- desc: "empty archive",
- before: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- tarball: map[string]string{},
- after: map[string]string{
- "tailscale": "v1",
- "tailscaled": "v1",
- },
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- // Swap out binaryPaths function to point at dummy file paths.
- tmp := t.TempDir()
- tailscalePath := filepath.Join(tmp, "tailscale")
- tailscaledPath := filepath.Join(tmp, "tailscaled")
- binaryPaths = func() (string, string, error) {
- return tailscalePath, tailscaledPath, nil
- }
- for name, content := range tt.before {
- if err := os.WriteFile(filepath.Join(tmp, name), []byte(content), 0755); err != nil {
- t.Fatal(err)
- }
- }
- tarPath := filepath.Join(tmp, "tailscale.tgz")
- genTarball(t, tarPath, tt.tarball)
- up := &Updater{Arguments: Arguments{Logf: t.Logf}}
- err := up.unpackLinuxTarball(tarPath)
- if err != nil {
- if !tt.wantErr {
- t.Fatalf("unexpected error: %v", err)
- }
- } else if tt.wantErr {
- t.Fatalf("unpack succeeded, expected an error")
- }
- gotAfter := make(map[string]string)
- err = filepath.WalkDir(tmp, func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
- if d.Type().IsDir() {
- return nil
- }
- if path == tarPath {
- return nil
- }
- content, err := os.ReadFile(path)
- if err != nil {
- return err
- }
- path = filepath.ToSlash(path)
- base := filepath.ToSlash(tmp)
- gotAfter[strings.TrimPrefix(path, base+"/")] = string(content)
- return nil
- })
- if err != nil {
- t.Fatal(err)
- }
- if !maps.Equal(gotAfter, tt.after) {
- t.Errorf("files after unpack: %+v, want %+v", gotAfter, tt.after)
- }
- })
- }
- }
- func genTarball(t *testing.T, path string, files map[string]string) {
- f, err := os.Create(path)
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- gw := gzip.NewWriter(f)
- defer gw.Close()
- tw := tar.NewWriter(gw)
- defer tw.Close()
- for file, content := range files {
- if err := tw.WriteHeader(&tar.Header{
- Name: file,
- Size: int64(len(content)),
- Mode: 0755,
- }); err != nil {
- t.Fatal(err)
- }
- if _, err := tw.Write([]byte(content)); err != nil {
- t.Fatal(err)
- }
- }
- }
- func TestWriteFileOverwrite(t *testing.T) {
- path := filepath.Join(t.TempDir(), "test")
- for i := range 2 {
- content := fmt.Sprintf("content %d", i)
- if err := writeFile(strings.NewReader(content), path, 0600); err != nil {
- t.Fatal(err)
- }
- got, err := os.ReadFile(path)
- if err != nil {
- t.Fatal(err)
- }
- if string(got) != content {
- t.Errorf("got content: %q, want: %q", got, content)
- }
- }
- }
- func TestWriteFileSymlink(t *testing.T) {
- // Test for a malicious symlink at the destination path.
- // f2 points to f1 and writeFile(f2) should not end up overwriting f1.
- tmp := t.TempDir()
- f1 := filepath.Join(tmp, "f1")
- if err := os.WriteFile(f1, []byte("old"), 0600); err != nil {
- t.Fatal(err)
- }
- f2 := filepath.Join(tmp, "f2")
- if err := os.Symlink(f1, f2); err != nil {
- t.Fatal(err)
- }
- if err := writeFile(strings.NewReader("new"), f2, 0600); err != nil {
- t.Errorf("writeFile(%q) failed: %v", f2, err)
- }
- want := map[string]string{
- f1: "old",
- f2: "new",
- }
- for f, content := range want {
- got, err := os.ReadFile(f)
- if err != nil {
- t.Fatal(err)
- }
- if string(got) != content {
- t.Errorf("%q: got content %q, want %q", f, got, content)
- }
- }
- }
- func TestCleanupOldDownloads(t *testing.T) {
- tests := []struct {
- desc string
- before []string
- symlinks map[string]string
- glob string
- after []string
- }{
- {
- desc: "MSIs",
- before: []string{
- "MSICache/tailscale-1.0.0.msi",
- "MSICache/tailscale-1.1.0.msi",
- "MSICache/readme.txt",
- },
- glob: "MSICache/*.msi",
- after: []string{
- "MSICache/readme.txt",
- },
- },
- {
- desc: "SPKs",
- before: []string{
- "tmp/tailscale-update-1/tailscale-1.0.0.spk",
- "tmp/tailscale-update-2/tailscale-1.1.0.spk",
- "tmp/readme.txt",
- "tmp/tailscale-update-3",
- "tmp/tailscale-update-4/tailscale-1.3.0",
- },
- glob: "tmp/tailscale-update*/*.spk",
- after: []string{
- "tmp/readme.txt",
- "tmp/tailscale-update-3",
- "tmp/tailscale-update-4/tailscale-1.3.0",
- },
- },
- {
- desc: "empty-target",
- before: []string{},
- glob: "tmp/tailscale-update*/*.spk",
- after: []string{},
- },
- {
- desc: "keep-dirs",
- before: []string{
- "tmp/tailscale-update-1/tailscale-1.0.0.spk",
- },
- glob: "tmp/tailscale-update*",
- after: []string{
- "tmp/tailscale-update-1/tailscale-1.0.0.spk",
- },
- },
- {
- desc: "no-follow-symlinks",
- before: []string{
- "MSICache/tailscale-1.0.0.msi",
- "MSICache/tailscale-1.1.0.msi",
- "MSICache/readme.txt",
- },
- symlinks: map[string]string{
- "MSICache/tailscale-1.3.0.msi": "MSICache/tailscale-1.0.0.msi",
- "MSICache/tailscale-1.4.0.msi": "MSICache/readme.txt",
- },
- glob: "MSICache/*.msi",
- after: []string{
- "MSICache/tailscale-1.3.0.msi",
- "MSICache/tailscale-1.4.0.msi",
- "MSICache/readme.txt",
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- dir := t.TempDir()
- for _, p := range tt.before {
- if err := os.MkdirAll(filepath.Join(dir, filepath.Dir(p)), 0700); err != nil {
- t.Fatal(err)
- }
- if err := os.WriteFile(filepath.Join(dir, p), []byte(tt.desc), 0600); err != nil {
- t.Fatal(err)
- }
- }
- for from, to := range tt.symlinks {
- if err := os.Symlink(filepath.Join(dir, to), filepath.Join(dir, from)); err != nil {
- t.Fatal(err)
- }
- }
- up := &Updater{Arguments: Arguments{Logf: t.Logf}}
- up.cleanupOldDownloads(filepath.Join(dir, tt.glob))
- var after []string
- if err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
- if !d.IsDir() {
- after = append(after, strings.TrimPrefix(filepath.ToSlash(path), filepath.ToSlash(dir)+"/"))
- }
- return nil
- }); err != nil {
- t.Fatal(err)
- }
- sort.Strings(after)
- sort.Strings(tt.after)
- if !slices.Equal(after, tt.after) {
- t.Errorf("got files after cleanup: %q, want: %q", after, tt.after)
- }
- })
- }
- }
- func TestParseUnraidPluginVersion(t *testing.T) {
- tests := []struct {
- plgPath string
- wantVer string
- wantErr string
- }{
- {plgPath: "testdata/tailscale-1.52.0.plg", wantVer: "1.52.0"},
- {plgPath: "testdata/tailscale-1.54.0.plg", wantVer: "1.54.0"},
- {plgPath: "testdata/tailscale-nover.plg", wantErr: "version not found in plg file"},
- {plgPath: "testdata/tailscale-nover-path-mentioned.plg", wantErr: "version not found in plg file"},
- }
- for _, tt := range tests {
- t.Run(tt.plgPath, func(t *testing.T) {
- got, err := parseUnraidPluginVersion(tt.plgPath)
- if got != tt.wantVer {
- t.Errorf("got version: %q, want %q", got, tt.wantVer)
- }
- var gotErr string
- if err != nil {
- gotErr = err.Error()
- }
- if gotErr != tt.wantErr {
- t.Errorf("got error: %q, want %q", gotErr, tt.wantErr)
- }
- })
- }
- }
- func TestConfirm(t *testing.T) {
- curTrack := CurrentTrack
- defer func() { CurrentTrack = curTrack }()
- tests := []struct {
- desc string
- fromTrack string
- toTrack string
- fromVer string
- toVer string
- confirm func(string) bool
- want bool
- }{
- {
- desc: "on latest stable",
- fromTrack: StableTrack,
- toTrack: StableTrack,
- fromVer: "1.66.0",
- toVer: "1.66.0",
- want: false,
- },
- {
- desc: "stable upgrade",
- fromTrack: StableTrack,
- toTrack: StableTrack,
- fromVer: "1.66.0",
- toVer: "1.68.0",
- want: true,
- },
- {
- desc: "unstable upgrade",
- fromTrack: UnstableTrack,
- toTrack: UnstableTrack,
- fromVer: "1.67.1",
- toVer: "1.67.2",
- want: true,
- },
- {
- desc: "from stable to unstable",
- fromTrack: StableTrack,
- toTrack: UnstableTrack,
- fromVer: "1.66.0",
- toVer: "1.67.1",
- want: true,
- },
- {
- desc: "from unstable to stable",
- fromTrack: UnstableTrack,
- toTrack: StableTrack,
- fromVer: "1.67.1",
- toVer: "1.66.0",
- want: true,
- },
- {
- desc: "confirm callback rejects",
- fromTrack: StableTrack,
- toTrack: StableTrack,
- fromVer: "1.66.0",
- toVer: "1.66.1",
- confirm: func(string) bool {
- return false
- },
- want: false,
- },
- {
- desc: "confirm callback allows",
- fromTrack: StableTrack,
- toTrack: StableTrack,
- fromVer: "1.66.0",
- toVer: "1.66.1",
- confirm: func(string) bool {
- return true
- },
- want: true,
- },
- {
- desc: "downgrade",
- fromTrack: StableTrack,
- toTrack: StableTrack,
- fromVer: "1.66.1",
- toVer: "1.66.0",
- want: false,
- },
- }
- for _, tt := range tests {
- t.Run(tt.desc, func(t *testing.T) {
- CurrentTrack = tt.fromTrack
- up := Updater{
- currentVersion: tt.fromVer,
- Arguments: Arguments{
- Track: tt.toTrack,
- Confirm: tt.confirm,
- Logf: t.Logf,
- },
- }
- if got := up.confirm(tt.toVer); got != tt.want {
- t.Errorf("got %v, want %v", got, tt.want)
- }
- })
- }
- }
|