| 1 | //go:build windows |
| 2 | |
| 3 | package desktoplauncher |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "os/exec" |
| 8 | "path/filepath" |
| 9 | "testing" |
| 10 | |
| 11 | "golang.org/x/sys/windows" |
| 12 | ) |
| 13 | |
| 14 | func TestResolveInstallRootThroughDirectoryJunction(t *testing.T) { |
| 15 | root := t.TempDir() |
| 16 | launcher := filepath.Join(root, "reasonix-launcher.exe") |
| 17 | if err := os.WriteFile(launcher, []byte("launcher"), 0o755); err != nil { |
| 18 | t.Fatal(err) |
| 19 | } |
| 20 | |
| 21 | junction := filepath.Join(t.TempDir(), "current") |
| 22 | output, err := exec.Command("cmd", "/c", "mklink", "/J", junction, root).CombinedOutput() |
| 23 | if err != nil { |
| 24 | t.Fatalf("create directory junction: %v: %s", err, output) |
| 25 | } |
| 26 | |
| 27 | got, err := resolveInstallRoot(filepath.Join(junction, "reasonix-launcher.exe")) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | gotInfo, err := os.Stat(got) |
| 32 | if err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | wantInfo, err := os.Stat(root) |
| 36 | if err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | if !os.SameFile(gotInfo, wantInfo) { |
| 40 | t.Fatalf("resolveInstallRoot() = %q, want %q", got, root) |
| 41 | } |
| 42 | location, err := classifyLaunchLocation(got) |
| 43 | if err != nil { |
| 44 | t.Fatal(err) |
| 45 | } |
| 46 | if location != launchLocationLocal { |
| 47 | t.Fatalf("junction target location = %q, want local", location) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | func TestNormalizeFinalWindowsPath(t *testing.T) { |
| 52 | tests := []struct { |
| 53 | name string |
| 54 | path string |
| 55 | want string |
| 56 | }{ |
| 57 | {name: "drive", path: `\\?\C:\Apps\Reasonix`, want: `C:\Apps\Reasonix`}, |
| 58 | {name: "UNC", path: `\\?\UNC\server\share\Reasonix`, want: `\\server\share\Reasonix`}, |
| 59 | {name: "volume GUID", path: `\\?\Volume{abc}\Reasonix`, want: `\\?\Volume{abc}\Reasonix`}, |
| 60 | {name: "ordinary", path: `C:\Apps\Reasonix`, want: `C:\Apps\Reasonix`}, |
| 61 | } |
| 62 | for _, test := range tests { |
| 63 | t.Run(test.name, func(t *testing.T) { |
| 64 | if got := normalizeFinalWindowsPath(test.path); got != test.want { |
| 65 | t.Fatalf("normalizeFinalWindowsPath(%q) = %q, want %q", test.path, got, test.want) |
| 66 | } |
| 67 | }) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | func TestClassifyLaunchLocation(t *testing.T) { |
| 72 | tests := []struct { |
| 73 | name string |
| 74 | path string |
| 75 | driveType uint32 |
| 76 | want launchLocation |
| 77 | wantErr bool |
| 78 | wantQuery bool |
| 79 | }{ |
| 80 | {name: "Parallels UNC", path: `\\psf\Home\Desktop\Reasonix`, want: launchLocationUNC}, |
| 81 | {name: "ordinary UNC", path: `\\server\share\Reasonix`, want: launchLocationUNC}, |
| 82 | {name: "mapped remote drive", path: `Z:\Reasonix`, driveType: windows.DRIVE_REMOTE, want: launchLocationRemoteDrive, wantQuery: true}, |
| 83 | {name: "local fixed drive", path: `C:\Reasonix`, driveType: windows.DRIVE_FIXED, want: launchLocationLocal, wantQuery: true}, |
| 84 | {name: "local removable drive", path: `E:\Reasonix`, driveType: windows.DRIVE_REMOVABLE, want: launchLocationLocal, wantQuery: true}, |
| 85 | {name: "unknown drive", path: `Q:\Reasonix`, driveType: windows.DRIVE_UNKNOWN, wantErr: true, wantQuery: true}, |
| 86 | {name: "missing volume", path: `Reasonix`, wantErr: true}, |
| 87 | } |
| 88 | for _, test := range tests { |
| 89 | t.Run(test.name, func(t *testing.T) { |
| 90 | queried := false |
| 91 | got, err := classifyLaunchLocationWith(test.path, func(*uint16) uint32 { |
| 92 | queried = true |
| 93 | return test.driveType |
| 94 | }) |
| 95 | if (err != nil) != test.wantErr { |
| 96 | t.Fatalf("error = %v, wantErr %v", err, test.wantErr) |
| 97 | } |
| 98 | if got != test.want { |
| 99 | t.Errorf("location = %q, want %q", got, test.want) |
| 100 | } |
| 101 | if queried != test.wantQuery { |
| 102 | t.Errorf("GetDriveType queried = %v, want %v", queried, test.wantQuery) |
| 103 | } |
| 104 | }) |
| 105 | } |
| 106 | } |
| 107 |