Files
snack/aur/rpc_test.go
Tai Groot eb999ad391 feat(aur): implement native AUR client
Native Go implementation using the AUR RPC v5 API for search/info
queries and git+makepkg+pacman for building and installing packages.

Implements:
- Manager (full: Install, Remove, Purge, Upgrade, Update, List, Search,
  Info, IsInstalled, Version)
- VersionQuerier (LatestVersion, ListUpgrades, UpgradeAvailable, VersionCmp)
- Cleaner (Autoremove via pacman, Clean build dir)
- PackageUpgrader (UpgradePackages)

Key design decisions:
- No CLI wrapper (paru/yay) — uses AUR RPC API directly + git clone
- Packages are built with makepkg and installed via pacman -U
- Foreign packages (pacman -Qm) are treated as AUR packages for List
- Batch RPC queries (rpcInfoMulti) for efficient upgrade checks
- Configurable build directory and makepkg flags
- Not added to detect.Default() candidates (AUR supplements pacman,
  not replaces it) but available via detect.ByName("aur")
2026-03-05 22:50:35 +00:00

185 lines
4.9 KiB
Go

package aur
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRPCSearch(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := rpcResponse{
ResultCount: 2,
Results: []rpcResult{
{Name: "yay", Version: "12.5.7-1", Description: "AUR helper"},
{Name: "yay-bin", Version: "12.5.7-1", Description: "AUR helper (binary)"},
},
Type: "search",
Version: 5,
}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
// Override the base URL for testing — we need to test the parsing
// Since we can't easily override the const, test the JSON parsing directly
var resp rpcResponse
httpResp, err := http.Get(srv.URL)
require.NoError(t, err)
defer httpResp.Body.Close()
require.NoError(t, json.NewDecoder(httpResp.Body).Decode(&resp))
assert.Equal(t, 2, resp.ResultCount)
assert.Equal(t, "yay", resp.Results[0].Name)
assert.Equal(t, "12.5.7-1", resp.Results[0].Version)
pkg := resp.Results[0].toPackage()
assert.Equal(t, "yay", pkg.Name)
assert.Equal(t, "12.5.7-1", pkg.Version)
assert.Equal(t, "aur", pkg.Repository)
}
func TestRPCInfo(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := rpcResponse{
ResultCount: 1,
Results: []rpcResult{
{
Name: "yay",
Version: "12.5.7-1",
Description: "Yet another yogurt",
Depends: []string{"pacman>6.1", "git"},
MakeDepends: []string{"go>=1.24"},
},
},
Type: "multiinfo",
Version: 5,
}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
var resp rpcResponse
httpResp, err := http.Get(srv.URL)
require.NoError(t, err)
defer httpResp.Body.Close()
require.NoError(t, json.NewDecoder(httpResp.Body).Decode(&resp))
assert.Equal(t, 1, resp.ResultCount)
r := resp.Results[0]
assert.Equal(t, "yay", r.Name)
assert.Equal(t, []string{"pacman>6.1", "git"}, r.Depends)
assert.Equal(t, []string{"go>=1.24"}, r.MakeDepends)
}
func TestRPCError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := rpcResponse{
Error: "Incorrect request type specified.",
Type: "error",
Version: 5,
}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
var resp rpcResponse
httpResp, err := http.Get(srv.URL)
require.NoError(t, err)
defer httpResp.Body.Close()
require.NoError(t, json.NewDecoder(httpResp.Body).Decode(&resp))
assert.Equal(t, "Incorrect request type specified.", resp.Error)
}
func TestRPCInfoMulti(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := rpcResponse{
ResultCount: 2,
Results: []rpcResult{
{Name: "yay", Version: "12.5.7-1"},
{Name: "paru", Version: "2.0.4-1"},
},
Type: "multiinfo",
Version: 5,
}
json.NewEncoder(w).Encode(resp)
}))
defer srv.Close()
var resp rpcResponse
httpResp, err := http.Get(srv.URL)
require.NoError(t, err)
defer httpResp.Body.Close()
require.NoError(t, json.NewDecoder(httpResp.Body).Decode(&resp))
assert.Equal(t, 2, resp.ResultCount)
// Simulate rpcInfoMulti result building
result := make(map[string]rpcResult, len(resp.Results))
for _, r := range resp.Results {
result[r.Name] = r
}
assert.Equal(t, "12.5.7-1", result["yay"].Version)
assert.Equal(t, "2.0.4-1", result["paru"].Version)
}
func TestToPackage(t *testing.T) {
r := rpcResult{
Name: "paru",
Version: "2.0.4-1",
Description: "Feature packed AUR helper",
URL: "https://github.com/Morganamilo/paru",
}
pkg := r.toPackage()
assert.Equal(t, "paru", pkg.Name)
assert.Equal(t, "2.0.4-1", pkg.Version)
assert.Equal(t, "Feature packed AUR helper", pkg.Description)
assert.Equal(t, "aur", pkg.Repository)
assert.False(t, pkg.Installed) // AUR search results aren't installed
}
func TestRPCSearchLive(t *testing.T) {
if testing.Short() {
t.Skip("skipping live AUR API test")
}
pkgs, err := rpcSearch(context.Background(), "yay")
require.NoError(t, err)
assert.NotEmpty(t, pkgs)
found := false
for _, p := range pkgs {
if p.Name == "yay" {
found = true
assert.NotEmpty(t, p.Version)
assert.Equal(t, "aur", p.Repository)
break
}
}
assert.True(t, found, "expected to find 'yay' in AUR search results")
}
func TestRPCInfoLive(t *testing.T) {
if testing.Short() {
t.Skip("skipping live AUR API test")
}
pkg, err := rpcInfo(context.Background(), "yay")
require.NoError(t, err)
assert.Equal(t, "yay", pkg.Name)
assert.NotEmpty(t, pkg.Version)
}
func TestRPCInfoNotFound(t *testing.T) {
if testing.Short() {
t.Skip("skipping live AUR API test")
}
_, err := rpcInfo(context.Background(), "this-package-definitely-does-not-exist-12345")
assert.Error(t, err)
assert.Contains(t, err.Error(), "not found")
}