package main import ( "os" "path/filepath" "strings" "testing" ) func TestTheManifestDeclaresNothingTheHostCouldNotInstall(t *testing.T) { m := readManifest(t) holdsTheBundle(t, m, "snapd") // snapd is not in the official repositories: no package, and no unit that only that package // brings, until the mesh's package repository builds it (research 027 question 1). if len(m.Resources) != 0 { t.Errorf("resources %v", m.Resources) } } const listAll = `Name Version Rev Tracking Publisher Notes bare 1.0 5 latest/stable canonical** base code 04c0d99f 266 latest/stable vscode** disabled,classic code 07f806f9 267 latest/stable vscode** classic gtk-common-themes 0.1-81-g442e511 1535 latest/stable canonical** - ` func TestListReadsEachRevisionAndMarksTheDisabledOnes(t *testing.T) { f := using(t, func(string, Cmd) Result { return ok(listAll) }) got, err := List() if err != nil || len(got.Snaps) != 4 || got.Disabled != 1 || got.Active != 3 { t.Fatalf("%+v %v", got, err) } if s := got.Snaps[1]; s.Name != "code" || s.Revision != "266" || !s.Disabled || s.Publisher != "vscode" || strings.Join(s.Notes, ",") != "disabled,classic" { t.Errorf("%+v", s) } if s := got.Snaps[3]; len(s.Notes) != 0 || s.Disabled { t.Errorf("%+v", s) } if f.lines()[0] != "snap list --all --unicode=never --color=never" { t.Errorf("%v", f.lines()) } } func TestToolsSayWhenSnapdIsNotInstalled(t *testing.T) { using(t, func(string, Cmd) Result { return Result{Status: 127, Error: "not-found"} }) if _, err := List(); err == nil || !strings.Contains(err.Error(), "not in the official repositories") { t.Fatalf("%v", err) } got, err := Status() if err != nil || got.Installed || !strings.Contains(strings.Join(got.Findings, ";"), "not installed") { t.Fatalf("%+v %v", got, err) } } func TestStatusNamesWhatIsWrongWithTheInstallation(t *testing.T) { dir := t.TempDir() wasLSM, wasRoot := lsmFile, snapRoot lsmFile, snapRoot = filepath.Join(dir, "lsm"), filepath.Join(dir, "snap") defer func() { lsmFile, snapRoot = wasLSM, wasRoot }() _ = os.WriteFile(lsmFile, []byte("capability,landlock,lockdown,yama,bpf\n"), 0o644) _ = os.Mkdir(snapRoot, 0o755) using(t, func(line string, c Cmd) Result { switch { case line == "snap version": return ok("snap 2.76.2-2\nsnapd 2.76.2-2\nseries 16\n") case strings.HasSuffix(line, "is-enabled snapd.service"), strings.HasSuffix(line, "is-enabled apparmor.service"): return Result{Status: 1, Stdout: "disabled\n"} case strings.Contains(line, "is-enabled"): return ok("enabled\n") case strings.Contains(line, "is-active snapd.service"): return ok("active\n") } return Result{Status: 3, Stdout: "inactive\n"} }) got, err := Status() if err != nil || !got.Installed || got.Version != "2.76.2-2" || got.AppArmor || !got.ClassicRoot || len(got.Units) != 4 { t.Fatalf("%+v %v", got, err) } if got.Units[1].Enabled != "disabled" || got.Units[1].Active != "active" { t.Errorf("socket-activated service: %+v", got.Units[1]) } all := strings.Join(got.Findings, ";") if !strings.Contains(all, "without their confinement") || !strings.Contains(all, "nothing enforces") || strings.Contains(all, "/snap does not exist") { t.Errorf("%s", all) } } func TestInfoReadsFieldsCommandsAndChannels(t *testing.T) { using(t, func(string, Cmd) Result { return ok(`name: code summary: Code editing. Redefined. publisher: Visual Studio Code (vscode**) license: unset description: | Visual Studio Code is a new choice of tool. commands: - code - code.url-handler tracking: latest/stable channels: latest/stable: 07f806f9 2026-09-30 (267) 543MB classic latest/candidate: ^ `) }) got, err := Info("code") if err != nil || got.Fields["summary"] != "Code editing. Redefined." || len(got.Commands) != 2 || got.Fields["tracking"] != "latest/stable" { t.Fatalf("%+v %v", got, err) } if got.Channels["latest/stable"] != "07f806f9 2026-09-30 (267) 543MB classic" || got.Fields["description"] != "Visual Studio Code is a new choice of tool." { t.Errorf("%+v", got) } } func TestUpdatesAndChangesReadNothingToDoAsEmpty(t *testing.T) { using(t, func(line string, c Cmd) Result { if strings.Contains(line, "refresh --list") { return Result{Stderr: "All snaps up to date.\n"} } return Result{Status: 1, Stderr: "error: no changes found\n"} }) u, err := Updates() if err != nil || u["count"] != 0 { t.Fatalf("%v %v", u, err) } c, err := Changes("") if err != nil || len(c["changes"].([]Change)) != 0 { t.Fatalf("%v %v", c, err) } if _, err := Changes("12; reboot"); err == nil { t.Error("a change id that is not a number") } } func TestChangesAndTasksAreReadByColumn(t *testing.T) { using(t, func(line string, c Cmd) Result { if strings.Contains(line, "tasks") { return ok("Status Spawn Ready Summary\nDone 2026-10-01T18:57:00+02:00 2026-10-01T18:57:10+02:00 Download snap \"code\" (267)\n") } return ok("ID Status Spawn Ready Summary\n42 Doing 2026-10-04T10:00:00+02:00 - Install \"hello\" snap\n") }) c, err := Changes("") ch := c["changes"].([]Change) if err != nil || len(ch) != 1 || ch[0].ID != "42" || ch[0].Ready != "" || ch[0].Summary != `Install "hello" snap` { t.Fatalf("%+v %v", c, err) } c, _ = Changes("42") if tasks := c["tasks"].([]Change); len(tasks) != 1 || tasks[0].Status != "Done" || tasks[0].Summary != `Download snap "code" (267)` { t.Errorf("%+v", c) } } func TestActsGoThroughSudoWithoutWaitingAndAnswerTheChange(t *testing.T) { f := using(t, func(line string, c Cmd) Result { if strings.Contains(line, "refresh") { return Result{Stderr: "All snaps up to date.\n"} } return ok("57\n") }) got, err := Install("hello-world", "latest/edge", true) if err != nil || got.Change != "57" || !strings.Contains(got.Follow, "57") { t.Fatalf("%+v %v", got, err) } if _, err := Remove("hello-world", true); err != nil { t.Fatal(err) } r, err := Refresh("") if err != nil || r.Change != "" || !strings.Contains(r.Said, "up to date") { t.Fatalf("%+v %v", r, err) } want := "sudo -n snap install --no-wait --channel=latest/edge --classic hello-world\n" + "sudo -n snap remove --no-wait --purge hello-world\n" + "sudo -n snap refresh --no-wait" if got := strings.Join(f.lines(), "\n"); got != want { t.Errorf("asked\n%s", got) } for _, bad := range []string{"--classic", "Hello", "a b", "../x"} { if err := checkSnapName(bad); err == nil { t.Errorf("%q accepted as a snap name", bad) } } if _, err := Install("x", "--dangerous", false); err == nil { t.Error("an option as a channel") } } func TestDiskUsageMeasuresEachRevisionAndWhatDisabledOnesTake(t *testing.T) { dir := t.TempDir() was := snapsDir snapsDir = dir defer func() { snapsDir = was }() for name, size := range map[string]int{"code_266.snap": 500, "code_267.snap": 510, "bare_5.snap": 4} { _ = os.WriteFile(filepath.Join(dir, name), make([]byte, size), 0o600) } using(t, func(string, Cmd) Result { return ok(listAll) }) got, err := DiskUsage() if err != nil || got.TotalBytes != 1014 || got.Reclaimable != 500 || len(got.Snaps) != 2 { t.Fatalf("%+v %v", got, err) } if c := got.Snaps[0]; c.Name != "code" || c.Bytes != 1010 || !c.Revisions[0].Disabled || c.Revisions[1].Disabled { t.Errorf("%+v", c) } }