| 1 | //go:build windows |
| 2 | |
| 3 | package appidentity |
| 4 | |
| 5 | import ( |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | |
| 11 | "golang.org/x/sys/windows" |
| 12 | ) |
| 13 | |
| 14 | func TestShortcutMigrationAcceptsShortAndLongPathsForTheSameFile(t *testing.T) { |
| 15 | migrationTestCOM(t) |
| 16 | root := filepath.Join(t.TempDir(), "Reasonix identity validation") |
| 17 | launcher := filepath.Join(root, "reasonix-launcher.exe") |
| 18 | target := filepath.Join(root, "versions", "v1.38.6", "app", "Reasonix.exe") |
| 19 | migrationTestFile(t, launcher) |
| 20 | migrationTestFile(t, target) |
| 21 | rootPtr, err := windows.UTF16PtrFromString(root) |
| 22 | if err != nil { |
| 23 | t.Fatal(err) |
| 24 | } |
| 25 | buffer := make([]uint16, windowsPathBuffer) |
| 26 | n, err := windows.GetShortPathName(rootPtr, &buffer[0], uint32(len(buffer))) |
| 27 | if err != nil || n >= uint32(len(buffer)) { |
| 28 | t.Fatalf("short path: length=%d err=%v", n, err) |
| 29 | } |
| 30 | shortRoot := windows.UTF16ToString(buffer[:n]) |
| 31 | if strings.EqualFold(shortRoot, root) { |
| 32 | t.Skip("the test volume does not create DOS short names") |
| 33 | } |
| 34 | path := filepath.Join(root, "Reasonix.lnk") |
| 35 | migrationTestShortcut(t, path, migrationShortcutState{ |
| 36 | target: filepath.Join(shortRoot, "versions", "v1.38.6", "app", "Reasonix.exe"), |
| 37 | id: "Reasonix", workingDirectory: shortRoot, showCmd: 1, |
| 38 | }) |
| 39 | changed, err := repairOwnedShortcut(path, shortRoot) |
| 40 | if err != nil || !changed { |
| 41 | t.Fatalf("short-root repair = %t, %v", changed, err) |
| 42 | } |
| 43 | got := migrationReadShortcut(t, path) |
| 44 | if !migrationSamePath(got.target, launcher) || got.id != AppUserModelID { |
| 45 | t.Fatalf("short-root repair returned %+v", got) |
| 46 | } |
| 47 | other := filepath.Join(t.TempDir(), "reasonix-launcher.exe") |
| 48 | if err := os.WriteFile(other, []byte("another installation"), 0o600); err != nil { |
| 49 | t.Fatal(err) |
| 50 | } |
| 51 | if migrationSamePath(got.target, other) { |
| 52 | t.Fatal("different files with the same executable name must not compare equal") |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | func TestCanonicalShortcutMigratesShortLegacyFilename(t *testing.T) { |
| 57 | migrationTestCOM(t) |
| 58 | root := filepath.Join(t.TempDir(), "Reasonix long installation path") |
| 59 | legacy := filepath.Join(root, "reasonix-launcher.exe") |
| 60 | canonical := filepath.Join(root, "Reasonix.exe") |
| 61 | migrationTestFile(t, legacy) |
| 62 | migrationTestFile(t, canonical) |
| 63 | ptr, err := windows.UTF16PtrFromString(legacy) |
| 64 | if err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | buffer := make([]uint16, windowsPathBuffer) |
| 68 | n, err := windows.GetShortPathName(ptr, &buffer[0], uint32(len(buffer))) |
| 69 | if err != nil || n >= uint32(len(buffer)) { |
| 70 | t.Fatalf("short path length=%d error=%v", n, err) |
| 71 | } |
| 72 | short := windows.UTF16ToString(buffer[:n]) |
| 73 | if strings.EqualFold(filepath.Base(short), filepath.Base(legacy)) { |
| 74 | t.Skip("volume does not create DOS short filenames") |
| 75 | } |
| 76 | link := filepath.Join(root, "Reasonix.lnk") |
| 77 | migrationTestShortcut(t, link, migrationShortcutState{target: short, icon: short, id: AppUserModelID, workingDirectory: root, showCmd: 1}) |
| 78 | if changed, err := repairOwnedShortcut(link, root); err != nil || !changed { |
| 79 | t.Fatalf("repair=%v, %v", changed, err) |
| 80 | } |
| 81 | got := migrationReadShortcut(t, link) |
| 82 | if !migrationSamePath(got.target, canonical) || !migrationSamePath(got.icon, canonical) { |
| 83 | t.Fatalf("short legacy entry was not migrated: %+v", got) |
| 84 | } |
| 85 | } |
| 86 |