Files
mesh-catalog/modules/jetbrains-toolbox/cmd/toolbox-tools/toolbox_test.go
T
jochen 75f25fca21 Add slack and jetbrains-toolbox: one start each, the apps kept as found
Slack comes from the AUR and Toolbox from JetBrains' self-updating tarball,
so neither is declared: the host installs official packages only, and a
pinned archive would fight Toolbox's own updates. Slack's start and the
operator's i3 window rules become one node-display-session contribution,
because Slack's own launch-on-login is a symlink in ~/.config/autostart.
Toolbox keeps its own autostart entry as its one start. The shared
desktop.go header now names all four bundles.
2026-10-05 15:05:55 +02:00

199 lines
8.0 KiB
Go

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)
}
}