package main import ( "encoding/json" "strings" "testing" ) const tb = testHome + "/" + toolboxDir // newToolbox is a Toolbox as the desktop machine keeps it: three IDEs, one on the EAP channel, its // launch-at-login switch at its default, its own autostart entry, an account no answer may name. func newToolbox(t *testing.T) *fake { f := newFake(t) f.write(tb+"/bin/jetbrains-toolbox", "\x7fELF") if err := chmodX(f, tb+"/bin/jetbrains-toolbox"); err != nil { t.Fatal(err) } f.write(tb+"/bin/build.txt", "3.2.0.65851") f.write(tb+"/.settings.json", `{"advanced":{"build_for_installed":true},"channel_rollback_max_history":1, "shell_scripts":{"location":"/home/operator/.local/share/JetBrains/Toolbox/scripts"},"ui":{"theme":"dark"}, "tools":{"update_all_automatically":true},"jetbrains_account":{"active":"1838624"}}`) f.write(tb+"/accounts.json", `{"accounts":[{"email":"operator@example.test","token":"secret-token"}]}`) f.write(tb+"/state.json", `{"version":1,"appVersion":"3.2.0.65851","tools":[ {"channelId":"RustRover-dd65","toolId":"RustRover","displayName":"RustRover","displayVersion":"2026.1 EAP","buildNumber":"261.21525.29", "installLocation":"/home/operator/.local/share/JetBrains/Toolbox/apps/rustrover","launchCommand":"x"}, {"channelId":"Rider-8c11","toolId":"Rider","displayName":"Rider","displayVersion":"2025.3.2","buildNumber":"253.30387.148", "installLocation":"/home/operator/.local/share/JetBrains/Toolbox/apps/rider"}, {"channelId":"../../escape","toolId":"Fleet","displayName":"Fleet","displayVersion":"1.48.261 Public Preview", "installLocation":"/home/operator/.local/share/JetBrains/Toolbox/apps/fleet"}]}`) f.write(tb+"/channels/RustRover-dd65.json", `{"channel":{"updateFilter":{"application_type":"RustRover", "quality_filter":{"name":"Early Access Program","order_value":40000}},"autoUpdate":true}}`) f.write(tb+"/channels/Rider-8c11.json", `{"channel":{"updateFilter":{"quality_filter":{"name":"Release"}}}}`) for _, d := range []string{"rustrover", "rider", "fleet", "webstorm-old"} { f.write(tb+"/apps/"+d+"/build.txt", "x") } f.write(testHome+"/.config/autostart/"+entryName, "[Desktop Entry]\nExec=/home/operator/.local/share/JetBrains/Toolbox/bin/jetbrains-toolbox --minimize\n"+ "X-GNOME-Autostart-enabled=true\n") f.answer = func(name string, args []string) Output { return Output{} } return f } func noSecrets(t *testing.T, v any) string { t.Helper() raw, err := json.Marshal(v) if err != nil { t.Fatal(err) } s := string(raw) for _, never := range []string{"1838624", "example.test", "secret-token", "/home/operator"} { if strings.Contains(s, never) { t.Errorf("the answer carries %q: %s", never, s) } } return s } func TestStatusListsTheIDEsWithTheirChannelsAndNoAccount(t *testing.T) { f := newToolbox(t) f.proc(4100, 1000, toolboxComm, []string{"/home/operator/.local/share/JetBrains/Toolbox/bin/jetbrains-toolbox", "--minimize"}, "session-4.scope") s, err := f.Status() if err != nil { t.Fatal(err) } noSecrets(t, s) if !s.Installed || s.Version != "3.2.0.65851" || len(s.Running) != 1 || !s.StartedBy.Starts { t.Fatalf("%+v", s) } set := s.Settings if !set.Found || !set.LaunchAtLogin || !set.LaunchDefault || !*set.UpdateTools || set.ShellScripts != "~/"+toolboxDir+"/scripts" || *set.RollbackKeeps != 1 { t.Fatalf("%+v", set) } st := s.State if st.AppVersion != "3.2.0.65851" || len(st.Tools) != 3 { t.Fatalf("%+v", st) } fleet, rider, rr := st.Tools[0], st.Tools[1], st.Tools[2] if rr.Name != "RustRover" || rr.Version != "2026.1 EAP" || rr.Channel != "Early Access Program" || rr.AutoUpdate == nil || !*rr.AutoUpdate || rr.Location != "~/"+toolboxDir+"/apps/rustrover" || !rr.Present { t.Fatalf("%+v", rr) } if rider.Channel != "Release" || rider.AutoUpdate != nil { t.Fatalf("%+v", rider) } // A channel id that would leave the channels directory is not read. if fleet.Name != "Fleet" || fleet.Channel != "" { t.Fatalf("%+v", fleet) } if len(st.Untracked) != 1 || st.Untracked[0] != "~/"+toolboxDir+"/apps/webstorm-old" { t.Fatalf("%q", st.Untracked) } } func TestOnlyToolboxsLauncherIsToolbox(t *testing.T) { f := newToolbox(t) f.proc(4100, 1000, toolboxComm, []string{"/home/operator/.local/share/JetBrains/Toolbox/bin/jetbrains-toolbox"}, "s.scope") // A program whose name the kernel cuts to the same comm. f.proc(4200, 1000, toolboxComm, []string{"/usr/lib/mesh/bundles/jetbrains-toolbox-tools"}, "s.scope") if r := f.running(); len(r) != 1 || r[0].PID != 4100 { t.Fatalf("%+v", r) } } func TestTheLaunchSwitchIsReadAndAbsentMeansOn(t *testing.T) { f := newToolbox(t) f.write(tb+"/.settings.json", `{"autostart":false}`) s, _ := f.settings() if s.LaunchAtLogin || s.LaunchDefault { t.Fatalf("%+v", s) } none := newFake(t) if s, err := none.settings(); err != nil || s.Found || !s.LaunchAtLogin { t.Fatalf("%+v %v", s, err) } if st, err := none.Status(); err != nil || st.Installed || len(st.State.Tools) != 0 { t.Fatalf("%+v %v", st, err) } } func TestRestartStartsToolboxMinimisedUnderTheServiceManager(t *testing.T) { f := newToolbox(t) if _, err := f.Restart(); err == nil || !strings.Contains(err.Error(), "no graphical session") { t.Fatalf("without a desktop: %v", err) } f.desktopSession() f.proc(4100, 1000, toolboxComm, []string{"jetbrains-toolbox"}, "session-4.scope") f.onStart = func(argv []string) { f.proc(9100, 1000, toolboxComm, argv, "app.slice/"+restartAs+".service") } a, err := f.Restart() if err != nil { t.Fatal(err) } if len(a.Ended) != 1 || a.Ended[0] != 4100 || len(a.Running) != 1 || a.Running[0].PID != 9100 || a.Running[0].Command != "~/"+toolboxDir+"/bin/jetbrains-toolbox --minimize" { t.Fatalf("%+v", a) } if !f.called("systemd-run --user --collect --quiet --unit=" + restartAs) { t.Fatalf("%q", f.calls) } gone := newFake(t) gone.desktopSession() if _, err := gone.Restart(); err == nil || !strings.Contains(err.Error(), "not installed") { t.Fatalf("%v", err) } } func TestCheckHoldsOneStartInAgreementWithTheSwitch(t *testing.T) { f := newToolbox(t) f.desktopSession() f.proc(4100, 1000, toolboxComm, []string{"jetbrains-toolbox"}, "session-4.scope") c, err := f.Check() if err != nil { t.Fatal(err) } noSecrets(t, c) if !c.OK || len(c.Starts) != 1 || len(c.Notes) != 1 { t.Fatalf("%+v", c) } // The laptop's way: the switch off, no entry, not running: no finding, a note. f.write(tb+"/.settings.json", `{"autostart":false}`) removeAll(f, testHome+"/.config/autostart/"+entryName) removeAll(f, "/proc/4100") if c, _ = f.Check(); !c.OK || len(c.Starts) != 0 || !strings.Contains(strings.Join(c.Notes, " "), "its setting is off") { t.Fatalf("%+v", c) } // Everything wrong at once. f.write(tb+"/.settings.json", `{}`) f.write(testHome+"/.config/i3/config.d/70-ide.conf", "exec --no-startup-id ~/.local/share/JetBrains/Toolbox/bin/jetbrains-toolbox\n") removeAll(f, tb+"/apps/rider") f.proc(4100, 1000, toolboxComm, []string{"jetbrains-toolbox"}, "s.scope") f.proc(4101, 1000, toolboxComm, []string{"jetbrains-toolbox"}, "s.scope") c, _ = f.Check() all := noSecrets(t, c) for _, want := range []string{"does not start with the session", "a second start: ~/.config/i3/config.d/70-ide.conf:1", "2 Toolbox processes run", "Rider 2025.3.2 is in Toolbox's list but not on disk"} { if !strings.Contains(all, want) { t.Errorf("no finding %q in %s", want, all) } } f.write(tb+"/.settings.json", `{"autostart":false}`) f.write(testHome+"/.config/autostart/"+entryName, "[Desktop Entry]\nExec=something-else\n") f.answer = func(name string, _ []string) Output { if name == "dex" { return Output{Code: 127, Err: ErrNotInstalled} } return Output{} } c, _ = f.Check() all = noSecrets(t, c) for _, want := range []string{"runs something-else, not Toolbox", "says not to launch at login, but its autostart entry is there", "dex"} { if !strings.Contains(all, want) { t.Errorf("no finding %q in %s", want, all) } } removeAll(f, tb+"/bin/jetbrains-toolbox") if c, _ = f.Check(); c.OK || len(c.Findings) != 1 || !strings.Contains(c.Findings[0].Do, "does not install it") { t.Fatalf("%+v", c) } }