Files
snack/dnf/dnf_integration_test.go
Tai Groot b12f956e45 test: exhaustive integration tests with codecov
- Root package unit tests: Targets, TargetNames, ApplyOptions, error sentinels
- Every provider integration test now covers:
  - All Manager interface methods (positive + negative cases)
  - GetCapabilities verification (assert expected interfaces)
  - VersionQuerier: LatestVersion, ListUpgrades, UpgradeAvailable, VersionCmp
  - Holder: Hold, ListHeld, Unhold (apt, dnf)
  - Cleaner: Autoremove, Clean
  - FileOwner: FileList, Owner (+ not-found cases)
  - RepoManager: ListRepos (apt, dnf, flatpak)
  - KeyManager: ListKeys (apt, dnf)
  - Grouper: GroupList, GroupInfo (pacman, dnf)
  - NameNormalizer: NormalizeName, ParseArch table tests (apt, dpkg, dnf, rpm)
- Containertest matrix: 5 distros (debian, alpine, arch, fedora39, fedora-latest)
- CI: coverage profiles uploaded per-job, merged in codecov job
- Added .gitignore for coverage files
2026-02-26 02:50:48 +00:00

405 lines
10 KiB
Go

//go:build integration
package dnf_test
import (
"context"
"testing"
"github.com/gogrlx/snack"
"github.com/gogrlx/snack/dnf"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestIntegration_DNF_Detection(t *testing.T) {
d := dnf.New()
if !d.Available() {
t.Skip("dnf not available")
}
t.Logf("IsDNF5: %v", d.IsDNF5())
}
func TestIntegration_DNF(t *testing.T) {
mgr := dnf.New()
if !mgr.Available() {
t.Skip("dnf not available")
}
ctx := context.Background()
assert.Equal(t, "dnf", mgr.Name())
caps := snack.GetCapabilities(mgr)
assert.True(t, caps.VersionQuery, "dnf should support VersionQuery")
assert.True(t, caps.Hold, "dnf should support Hold")
assert.True(t, caps.Clean, "dnf should support Clean")
assert.True(t, caps.FileOwnership, "dnf should support FileOwnership")
assert.True(t, caps.RepoManagement, "dnf should support RepoManagement")
assert.True(t, caps.KeyManagement, "dnf should support KeyManagement")
assert.True(t, caps.Groups, "dnf should support Groups")
assert.True(t, caps.NameNormalize, "dnf should support NameNormalize")
t.Run("Update", func(t *testing.T) {
require.NoError(t, mgr.Update(ctx))
})
t.Run("Search", func(t *testing.T) {
pkgs, err := mgr.Search(ctx, "curl")
require.NoError(t, err)
require.NotEmpty(t, pkgs)
found := false
for _, p := range pkgs {
if p.Name == "curl" {
found = true
assert.NotEmpty(t, p.Description)
break
}
}
assert.True(t, found, "curl should appear in search results")
})
t.Run("Search_NoResults", func(t *testing.T) {
pkgs, err := mgr.Search(ctx, "xyznonexistentpackage999")
require.NoError(t, err)
assert.Empty(t, pkgs)
})
t.Run("Info", func(t *testing.T) {
pkg, err := mgr.Info(ctx, "bash")
require.NoError(t, err)
require.NotNil(t, pkg)
assert.Equal(t, "bash", pkg.Name)
assert.NotEmpty(t, pkg.Version)
})
t.Run("Info_NotFound", func(t *testing.T) {
_, err := mgr.Info(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
assert.ErrorIs(t, err, snack.ErrNotFound)
})
t.Run("Install_Single", func(t *testing.T) {
_ = mgr.Remove(ctx, snack.Targets("tree"), snack.WithAssumeYes())
err := mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes())
require.NoError(t, err)
})
t.Run("IsInstalled_True", func(t *testing.T) {
installed, err := mgr.IsInstalled(ctx, "tree")
require.NoError(t, err)
assert.True(t, installed)
})
t.Run("IsInstalled_False", func(t *testing.T) {
installed, err := mgr.IsInstalled(ctx, "xyznonexistentpackage999")
require.NoError(t, err)
assert.False(t, installed)
})
t.Run("Version_Installed", func(t *testing.T) {
ver, err := mgr.Version(ctx, "tree")
require.NoError(t, err)
assert.NotEmpty(t, ver)
t.Logf("tree version: %s", ver)
})
t.Run("Version_NotInstalled", func(t *testing.T) {
_, err := mgr.Version(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
assert.ErrorIs(t, err, snack.ErrNotInstalled)
})
t.Run("List_ContainsInstalled", func(t *testing.T) {
pkgs, err := mgr.List(ctx)
require.NoError(t, err)
require.NotEmpty(t, pkgs)
found := false
for _, p := range pkgs {
if p.Name == "tree" {
found = true
assert.NotEmpty(t, p.Version)
break
}
}
assert.True(t, found, "tree should be in installed list")
})
t.Run("Install_Multiple", func(t *testing.T) {
err := mgr.Install(ctx, snack.Targets("tree", "less"), snack.WithAssumeYes())
require.NoError(t, err)
for _, pkg := range []string{"tree", "less"} {
installed, err := mgr.IsInstalled(ctx, pkg)
require.NoError(t, err)
assert.True(t, installed, "%s should be installed", pkg)
}
})
t.Run("Install_WithRefresh", func(t *testing.T) {
err := mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes(), snack.WithRefresh())
require.NoError(t, err)
})
t.Run("Purge", func(t *testing.T) {
err := mgr.Purge(ctx, snack.Targets("less"), snack.WithAssumeYes())
require.NoError(t, err)
installed, err := mgr.IsInstalled(ctx, "less")
require.NoError(t, err)
assert.False(t, installed)
})
t.Run("Remove", func(t *testing.T) {
err := mgr.Remove(ctx, snack.Targets("tree"), snack.WithAssumeYes())
require.NoError(t, err)
installed, err := mgr.IsInstalled(ctx, "tree")
require.NoError(t, err)
assert.False(t, installed)
})
// --- VersionQuerier ---
t.Run("VersionQuerier", func(t *testing.T) {
vq, ok := mgr.(snack.VersionQuerier)
require.True(t, ok)
t.Run("LatestVersion", func(t *testing.T) {
ver, err := vq.LatestVersion(ctx, "curl")
require.NoError(t, err)
assert.NotEmpty(t, ver)
t.Logf("curl latest: %s", ver)
})
t.Run("LatestVersion_NotFound", func(t *testing.T) {
_, err := vq.LatestVersion(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
})
t.Run("ListUpgrades", func(t *testing.T) {
pkgs, err := vq.ListUpgrades(ctx)
require.NoError(t, err)
t.Logf("upgradable packages: %d", len(pkgs))
for _, p := range pkgs {
assert.NotEmpty(t, p.Name)
assert.NotEmpty(t, p.Version)
}
})
t.Run("UpgradeAvailable", func(t *testing.T) {
avail, err := vq.UpgradeAvailable(ctx, "bash")
require.NoError(t, err)
_ = avail
})
t.Run("VersionCmp", func(t *testing.T) {
// rpmdev-vercmp may not be installed; skip if not available
cmp, err := vq.VersionCmp(ctx, "1.0-1", "1.0-2")
if err != nil {
t.Skip("rpmdev-vercmp not available")
}
assert.Equal(t, -1, cmp)
cmp, err = vq.VersionCmp(ctx, "2.0-1", "1.0-1")
require.NoError(t, err)
assert.Equal(t, 1, cmp)
cmp, err = vq.VersionCmp(ctx, "1.0-1", "1.0-1")
require.NoError(t, err)
assert.Equal(t, 0, cmp)
})
})
// --- Holder ---
t.Run("Holder", func(t *testing.T) {
h, ok := mgr.(snack.Holder)
require.True(t, ok)
// Install tree for hold tests; also ensure versionlock plugin
_ = mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes())
t.Run("Hold", func(t *testing.T) {
err := h.Hold(ctx, []string{"tree"})
if err != nil {
t.Skip("versionlock plugin not available:", err)
}
})
t.Run("ListHeld", func(t *testing.T) {
held, err := h.ListHeld(ctx)
if err != nil {
t.Skip("versionlock plugin not available:", err)
}
found := false
for _, p := range held {
if p.Name == "tree" {
found = true
break
}
}
assert.True(t, found, "tree should be in held list")
})
t.Run("Unhold", func(t *testing.T) {
err := h.Unhold(ctx, []string{"tree"})
if err != nil {
t.Skip("versionlock plugin not available:", err)
}
})
_ = mgr.Remove(ctx, snack.Targets("tree"), snack.WithAssumeYes())
})
// --- Cleaner ---
t.Run("Cleaner", func(t *testing.T) {
cl, ok := mgr.(snack.Cleaner)
require.True(t, ok)
t.Run("Autoremove", func(t *testing.T) {
err := cl.Autoremove(ctx)
require.NoError(t, err)
})
t.Run("Clean", func(t *testing.T) {
err := cl.Clean(ctx)
require.NoError(t, err)
})
})
// --- FileOwner ---
t.Run("FileOwner", func(t *testing.T) {
fo, ok := mgr.(snack.FileOwner)
require.True(t, ok)
_ = mgr.Install(ctx, snack.Targets("tree"), snack.WithAssumeYes())
t.Run("FileList", func(t *testing.T) {
files, err := fo.FileList(ctx, "tree")
require.NoError(t, err)
require.NotEmpty(t, files)
found := false
for _, f := range files {
if f == "/usr/bin/tree" {
found = true
break
}
}
assert.True(t, found, "/usr/bin/tree should be in file list")
})
t.Run("FileList_NotInstalled", func(t *testing.T) {
_, err := fo.FileList(ctx, "xyznonexistentpackage999")
assert.Error(t, err)
assert.ErrorIs(t, err, snack.ErrNotInstalled)
})
t.Run("Owner", func(t *testing.T) {
owner, err := fo.Owner(ctx, "/usr/bin/tree")
require.NoError(t, err)
assert.Contains(t, owner, "tree")
})
t.Run("Owner_NotFound", func(t *testing.T) {
_, err := fo.Owner(ctx, "/nonexistent/path/xyz")
assert.Error(t, err)
})
_ = mgr.Remove(ctx, snack.Targets("tree"), snack.WithAssumeYes())
})
// --- RepoManager ---
t.Run("RepoManager", func(t *testing.T) {
rm, ok := mgr.(snack.RepoManager)
require.True(t, ok)
t.Run("ListRepos", func(t *testing.T) {
repos, err := rm.ListRepos(ctx)
require.NoError(t, err)
require.NotEmpty(t, repos)
for _, r := range repos {
assert.NotEmpty(t, r.ID)
}
t.Logf("repos: %d", len(repos))
})
})
// --- KeyManager ---
t.Run("KeyManager", func(t *testing.T) {
km, ok := mgr.(snack.KeyManager)
require.True(t, ok)
t.Run("ListKeys", func(t *testing.T) {
keys, err := km.ListKeys(ctx)
require.NoError(t, err)
require.NotEmpty(t, keys, "Fedora should have GPG keys")
t.Logf("keys: %d", len(keys))
})
})
// --- Grouper ---
t.Run("Grouper", func(t *testing.T) {
g, ok := mgr.(snack.Grouper)
require.True(t, ok)
t.Run("GroupList", func(t *testing.T) {
groups, err := g.GroupList(ctx)
require.NoError(t, err)
require.NotEmpty(t, groups)
t.Logf("groups: %d", len(groups))
})
t.Run("GroupInfo", func(t *testing.T) {
// Use a group that should exist
groups, err := g.GroupList(ctx)
require.NoError(t, err)
require.NotEmpty(t, groups)
pkgs, err := g.GroupInfo(ctx, groups[0])
// Some groups may not have packages queryable by name
if err == nil {
t.Logf("group %q packages: %d", groups[0], len(pkgs))
}
})
t.Run("GroupInfo_NotFound", func(t *testing.T) {
_, err := g.GroupInfo(ctx, "xyznonexistentgroup999")
assert.Error(t, err)
})
})
// --- NameNormalizer ---
t.Run("NameNormalizer", func(t *testing.T) {
nn, ok := mgr.(snack.NameNormalizer)
require.True(t, ok)
tests := []struct {
input, wantName, wantArch string
}{
{"curl.x86_64", "curl", "x86_64"},
{"bash.aarch64", "bash", "aarch64"},
{"python3.noarch", "python3", "noarch"},
{"python3.11.x86_64", "python3.11", "x86_64"},
{"glibc", "glibc", ""},
}
for _, tt := range tests {
t.Run("NormalizeName_"+tt.input, func(t *testing.T) {
got := nn.NormalizeName(tt.input)
assert.Equal(t, tt.wantName, got)
})
t.Run("ParseArch_"+tt.input, func(t *testing.T) {
name, arch := nn.ParseArch(tt.input)
assert.Equal(t, tt.wantName, name)
assert.Equal(t, tt.wantArch, arch)
})
}
})
// --- Upgrade ---
t.Run("Upgrade", func(t *testing.T) {
err := mgr.Upgrade(ctx, snack.WithAssumeYes())
require.NoError(t, err)
})
}