package plan import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" ) const golden = `{ "repo": [{"name":"linux","old":"6.16.7-1","new":"6.16.8-1","repo":"core", "download":1468006,"delta":-1048576}], "aur": [{"name":"enca-bin","old":"1.19-4","new":"1.19-5","tag":"!!", "note":"will be skipped","details":["pipes a download into a shell"]}], "vcs": [{"name":"helix-git","delta":"r9021"}], "checks": [{"ok":false,"label":"databases","detail":"2d old · refreshed"}], "flatpak": [], "vulnerabilities": {"fixable":["vim"],"unfixable":[]}, "hygiene": {"cache_reclaimable":993710975,"pacnew":["/etc/x.pacnew"], "orphans":[],"reboot":"","stale_processes":[{"pid":1,"name":"x","path":"/usr/bin/x"}]}, "audits": {"enca-bin":[{"rule":"pipe-to-shell","severity":"BLOCK", "title":"pipes a download into a shell","evidence":"curl x | sh"}]} }` func decode(t *testing.T) *Plan { t.Helper() var p Plan if err := json.NewDecoder(strings.NewReader(golden)).Decode(&p); err != nil { t.Fatalf("decode: %v", err) } return &p } func TestDecodeReadsTheBackendContract(t *testing.T) { p := decode(t) if len(p.Repo) != 1 || p.Repo[0].Name != "linux" { t.Fatalf("repo not decoded: %+v", p.Repo) } if p.Repo[0].Delta != -1048576 { t.Errorf("delta = %d, want -1048576", p.Repo[0].Delta) } if p.Aur[0].Tag != "!!" { t.Errorf("tag = %q", p.Aur[0].Tag) } if got := p.Audits["enca-bin"][0].Severity; got != "BLOCK" { t.Errorf("severity = %q", got) } if p.Hygiene.CacheReclaimable != 993710975 { t.Errorf("cache = %d", p.Hygiene.CacheReclaimable) } if len(p.Hygiene.Stale) != 1 || p.Hygiene.Stale[0].PID != 1 { t.Errorf("stale not decoded: %+v", p.Hygiene.Stale) } } func TestUnknownFieldsAreTolerated(t *testing.T) { // the two halves must be updatable independently, so a field this side has // never heard of cannot be a parse failure var p Plan in := `{"repo":[],"something_new":{"nested":[1,2]},"aur":[]}` if err := json.NewDecoder(strings.NewReader(in)).Decode(&p); err != nil { t.Fatalf("a newer backend broke the older front end: %v", err) } } func TestCountsReportBlockedPackages(t *testing.T) { repo, aur, blocked := decode(t).Counts() if repo != 1 || aur != 1 || blocked != 1 { t.Errorf("Counts = %d,%d,%d want 1,1,1", repo, aur, blocked) } } func TestFindingGlyphMatchesThePythonRenderer(t *testing.T) { // the two front ends must use the same marker for the same severity cases := map[string]string{"BLOCK": "!!", "LOOK": "!", "INFO": "·", "": "·"} for severity, want := range cases { if got := (Finding{Severity: severity}).Glyph(); got != want { t.Errorf("Glyph(%q) = %q, want %q", severity, got, want) } } } func TestBackendErrorCarriesStderr(t *testing.T) { err := &Error{Args: []string{"python3", "-m", "updater"}, Stderr: "boom: no such module\n", Err: os.ErrNotExist} msg := err.Error() if !strings.Contains(msg, "boom: no such module") { t.Errorf("stderr was dropped: %q", msg) } if !strings.Contains(msg, "python3") { t.Errorf("the command was dropped: %q", msg) } } func TestBackendErrorFallsBackToTheUnderlyingError(t *testing.T) { err := &Error{Args: []string{"python3"}, Err: os.ErrPermission} if !strings.Contains(err.Error(), "permission") { t.Errorf("fallback message missing: %q", err.Error()) } } // TestLoadAgainstTheRealBackend is the integration test: it runs the actual // Python entry point and parses its actual output. Skipped when the tree is not // reachable, so the suite still runs on a machine without it. func TestLoadAgainstTheRealBackend(t *testing.T) { root, err := filepath.Abs("../..") if err != nil { t.Skip("cannot resolve the repo root") } if _, err := os.Stat(filepath.Join(root, "..", "updater", "cli.py")); err != nil { t.Skip("python backend not present next to the Go module") } b := Backend{Python: "python3", Module: "updater", Env: []string{"PYTHONPATH=" + filepath.Dir(root)}, Args: []string{"--offline", "--no-security"}} doc, err := b.Load(context.Background()) if err != nil { t.Skipf("backend unavailable here: %v", err) } if doc == nil { t.Fatal("nil plan with no error") } // stdout must have been pure JSON, which Load would have failed on otherwise if doc.Checks == nil { t.Log("note: no checks reported") } }