add test for the wall app
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
c4776d81dd
commit
9e287e4e6a
15 changed files with 3022 additions and 1 deletions
278
internal/clone/apt/metadata_gen_test.go
Normal file
278
internal/clone/apt/metadata_gen_test.go
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
package apt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testCfg = Config{
|
||||
Suite: "bookworm",
|
||||
Components: []string{"main"},
|
||||
Architectures: []string{"amd64"},
|
||||
}
|
||||
|
||||
// buildPackagesGz creates a gzipped Packages file from a list of stanzas.
|
||||
func buildPackagesGz(t *testing.T, stanzas []string) []byte {
|
||||
t.Helper()
|
||||
plain := []byte(strings.Join(stanzas, "\n\n") + "\n")
|
||||
|
||||
var buf bytes.Buffer
|
||||
gzw := gzip.NewWriter(&buf)
|
||||
gzw.Write(plain)
|
||||
gzw.Close()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func makeStanza(pkg, version, filename, sha256 string, size int) string {
|
||||
return strings.Join([]string{
|
||||
"Package: " + pkg,
|
||||
"Version: " + version,
|
||||
"Architecture: amd64",
|
||||
"Filename: " + filename,
|
||||
"SHA256: " + sha256,
|
||||
"Size: " + itoa(size),
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func itoa(n int) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
buf := make([]byte, 0, 10)
|
||||
for n > 0 {
|
||||
buf = append([]byte{byte('0' + n%10)}, buf...)
|
||||
n /= 10
|
||||
}
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// setupAptRepo creates the directory structure and writes the upstream
|
||||
// Packages.gz for component/arch. It creates the given deb files on disk.
|
||||
func setupAptRepo(t *testing.T, stanzas []string, presentOnDisk []string) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
|
||||
binDir := filepath.Join(dir, "dists", testCfg.Suite, "main", "binary-amd64")
|
||||
os.MkdirAll(binDir, 0o755)
|
||||
os.MkdirAll(filepath.Join(dir, "pool"), 0o755)
|
||||
|
||||
// Write upstream Packages.gz.
|
||||
gzData := buildPackagesGz(t, stanzas)
|
||||
if err := os.WriteFile(filepath.Join(binDir, upstreamPackagesGz), gzData, 0o644); err != nil {
|
||||
t.Fatalf("write upstream Packages.gz: %v", err)
|
||||
}
|
||||
|
||||
// Create selected deb files on disk.
|
||||
for _, filename := range presentOnDisk {
|
||||
dest := filepath.Join(dir, filepath.FromSlash(filename))
|
||||
os.MkdirAll(filepath.Dir(dest), 0o755)
|
||||
if err := os.WriteFile(dest, []byte("fake-deb"), 0o644); err != nil {
|
||||
t.Fatalf("write deb %s: %v", filename, err)
|
||||
}
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
// readLocalPackages reads and parses the local Packages file (plain text).
|
||||
func readLocalPackages(t *testing.T, dir string) []DebPackage {
|
||||
t.Helper()
|
||||
path := filepath.Join(dir, "dists", testCfg.Suite, "main", "binary-amd64", "Packages")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read Packages: %v", err)
|
||||
}
|
||||
pkgs, err := ParsePackages(data)
|
||||
if err != nil {
|
||||
t.Fatalf("parse Packages: %v", err)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// --- InitEmptyRepo ---
|
||||
|
||||
func TestInitEmptyRepo_createsStructure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := InitEmptyRepo(dir, testCfg); err != nil {
|
||||
t.Fatalf("InitEmptyRepo: %v", err)
|
||||
}
|
||||
|
||||
for _, path := range []string{
|
||||
filepath.Join(dir, "pool"),
|
||||
filepath.Join(dir, "dists", testCfg.Suite),
|
||||
filepath.Join(dir, "dists", testCfg.Suite, "main", "binary-amd64"),
|
||||
filepath.Join(dir, "dists", testCfg.Suite, "Release"),
|
||||
} {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Errorf("expected %s to exist: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitEmptyRepo_releaseIsParseable(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
InitEmptyRepo(dir, testCfg)
|
||||
|
||||
data, _ := os.ReadFile(filepath.Join(dir, "dists", testCfg.Suite, "Release"))
|
||||
rf, err := ParseRelease(data)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseRelease on generated file: %v", err)
|
||||
}
|
||||
if rf.Suite != testCfg.Suite {
|
||||
t.Errorf("Suite: got %q, want %q", rf.Suite, testCfg.Suite)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitEmptyRepo_idempotent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
InitEmptyRepo(dir, testCfg)
|
||||
|
||||
releaseBefore, _ := os.ReadFile(filepath.Join(dir, "dists", testCfg.Suite, "Release"))
|
||||
InitEmptyRepo(dir, testCfg) // second call
|
||||
releaseAfter, _ := os.ReadFile(filepath.Join(dir, "dists", testCfg.Suite, "Release"))
|
||||
|
||||
if !bytes.Equal(releaseBefore, releaseAfter) {
|
||||
t.Error("second InitEmptyRepo modified Release file")
|
||||
}
|
||||
}
|
||||
|
||||
// --- RegenerateMetadata ---
|
||||
|
||||
func TestRegenerateMetadata_keepsOnlyPresentFiles(t *testing.T) {
|
||||
stanzas := []string{
|
||||
makeStanza("curl", "7.88", "pool/main/c/curl/curl_7.88_amd64.deb", "aaa", 100),
|
||||
makeStanza("wget", "1.21", "pool/main/w/wget/wget_1.21_amd64.deb", "bbb", 200),
|
||||
makeStanza("vim", "9.0", "pool/main/v/vim/vim_9.0_amd64.deb", "ccc", 300),
|
||||
}
|
||||
// Only curl and vim are present.
|
||||
present := []string{
|
||||
"pool/main/c/curl/curl_7.88_amd64.deb",
|
||||
"pool/main/v/vim/vim_9.0_amd64.deb",
|
||||
}
|
||||
dir := setupAptRepo(t, stanzas, present)
|
||||
|
||||
if err := RegenerateMetadata(dir, testCfg); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
|
||||
pkgs := readLocalPackages(t, dir)
|
||||
if len(pkgs) != 2 {
|
||||
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||
}
|
||||
names := map[string]bool{pkgs[0].Package: true, pkgs[1].Package: true}
|
||||
if !names["curl"] {
|
||||
t.Error("expected curl in output")
|
||||
}
|
||||
if !names["vim"] {
|
||||
t.Error("expected vim in output")
|
||||
}
|
||||
if names["wget"] {
|
||||
t.Error("wget should be filtered out (not on disk)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_allAbsent(t *testing.T) {
|
||||
stanzas := []string{
|
||||
makeStanza("curl", "7.88", "pool/main/c/curl/curl_7.88_amd64.deb", "aaa", 100),
|
||||
}
|
||||
dir := setupAptRepo(t, stanzas, nil) // nothing on disk
|
||||
|
||||
if err := RegenerateMetadata(dir, testCfg); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
|
||||
pkgs := readLocalPackages(t, dir)
|
||||
if len(pkgs) != 0 {
|
||||
t.Errorf("expected 0 packages, got %d", len(pkgs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_writesRelease(t *testing.T) {
|
||||
stanzas := []string{
|
||||
makeStanza("curl", "7.88", "pool/main/c/curl/curl_7.88_amd64.deb", "aaa", 100),
|
||||
}
|
||||
dir := setupAptRepo(t, stanzas, []string{"pool/main/c/curl/curl_7.88_amd64.deb"})
|
||||
|
||||
if err := RegenerateMetadata(dir, testCfg); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
|
||||
releasePath := filepath.Join(dir, "dists", testCfg.Suite, "Release")
|
||||
data, err := os.ReadFile(releasePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Release not written: %v", err)
|
||||
}
|
||||
|
||||
rf, err := ParseRelease(data)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseRelease: %v", err)
|
||||
}
|
||||
if rf.Suite != testCfg.Suite {
|
||||
t.Errorf("Suite: got %q, want %q", rf.Suite, testCfg.Suite)
|
||||
}
|
||||
// Release must reference both Packages and Packages.gz.
|
||||
paths := map[string]bool{}
|
||||
for _, e := range rf.SHA256 {
|
||||
paths[e.Path] = true
|
||||
}
|
||||
if !paths["main/binary-amd64/Packages"] {
|
||||
t.Error("Release missing main/binary-amd64/Packages entry")
|
||||
}
|
||||
if !paths["main/binary-amd64/Packages.gz"] {
|
||||
t.Error("Release missing main/binary-amd64/Packages.gz entry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_writesPackagesGz(t *testing.T) {
|
||||
stanzas := []string{
|
||||
makeStanza("curl", "7.88", "pool/main/c/curl/curl_7.88_amd64.deb", "aaa", 100),
|
||||
}
|
||||
dir := setupAptRepo(t, stanzas, []string{"pool/main/c/curl/curl_7.88_amd64.deb"})
|
||||
|
||||
RegenerateMetadata(dir, testCfg)
|
||||
|
||||
gzPath := filepath.Join(dir, "dists", testCfg.Suite, "main", "binary-amd64", "Packages.gz")
|
||||
if _, err := os.Stat(gzPath); err != nil {
|
||||
t.Errorf("Packages.gz not written: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_noUpstream_skips(t *testing.T) {
|
||||
// No .upstream-Packages.gz — should return nil (skip silently).
|
||||
dir := t.TempDir()
|
||||
os.MkdirAll(filepath.Join(dir, "dists", testCfg.Suite, "main", "binary-amd64"), 0o755)
|
||||
|
||||
if err := RegenerateMetadata(dir, testCfg); err != nil {
|
||||
t.Errorf("expected no error when upstream missing, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_alwaysFiltersFromUpstream(t *testing.T) {
|
||||
// Two successive regens must always start from the upstream list.
|
||||
stanzas := []string{
|
||||
makeStanza("curl", "7.88", "pool/main/c/curl/curl_7.88_amd64.deb", "aaa", 100),
|
||||
makeStanza("wget", "1.21", "pool/main/w/wget/wget_1.21_amd64.deb", "bbb", 200),
|
||||
}
|
||||
dir := setupAptRepo(t, stanzas, []string{"pool/main/c/curl/curl_7.88_amd64.deb"})
|
||||
|
||||
// First regen: only curl.
|
||||
RegenerateMetadata(dir, testCfg)
|
||||
if len(readLocalPackages(t, dir)) != 1 {
|
||||
t.Fatal("first regen: expected 1 package")
|
||||
}
|
||||
|
||||
// Add wget, regen again.
|
||||
dest := filepath.Join(dir, "pool/main/w/wget/wget_1.21_amd64.deb")
|
||||
os.MkdirAll(filepath.Dir(dest), 0o755)
|
||||
os.WriteFile(dest, []byte("fake"), 0o644)
|
||||
|
||||
RegenerateMetadata(dir, testCfg)
|
||||
pkgs := readLocalPackages(t, dir)
|
||||
if len(pkgs) != 2 {
|
||||
t.Fatalf("second regen: expected 2 packages, got %d", len(pkgs))
|
||||
}
|
||||
}
|
||||
138
internal/clone/apt/packages_test.go
Normal file
138
internal/clone/apt/packages_test.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package apt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
const sampleStanza = `Package: curl
|
||||
Version: 7.88.1-10+deb12u5
|
||||
Architecture: amd64
|
||||
Filename: pool/main/c/curl/curl_7.88.1-10+deb12u5_amd64.deb
|
||||
SHA256: abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890
|
||||
Size: 314456`
|
||||
|
||||
const sampleStanza2 = `Package: wget
|
||||
Version: 1.21.3-1+b1
|
||||
Architecture: amd64
|
||||
Filename: pool/main/w/wget/wget_1.21.3-1+b1_amd64.deb
|
||||
SHA256: 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff
|
||||
Size: 512000`
|
||||
|
||||
func TestParsePackages_single(t *testing.T) {
|
||||
pkgs, err := ParsePackages([]byte(sampleStanza))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(pkgs) != 1 {
|
||||
t.Fatalf("expected 1 package, got %d", len(pkgs))
|
||||
}
|
||||
p := pkgs[0]
|
||||
if p.Package != "curl" {
|
||||
t.Errorf("Package: got %q, want %q", p.Package, "curl")
|
||||
}
|
||||
if p.Version != "7.88.1-10+deb12u5" {
|
||||
t.Errorf("Version: got %q, want %q", p.Version, "7.88.1-10+deb12u5")
|
||||
}
|
||||
if p.Architecture != "amd64" {
|
||||
t.Errorf("Architecture: got %q, want %q", p.Architecture, "amd64")
|
||||
}
|
||||
if p.Filename != "pool/main/c/curl/curl_7.88.1-10+deb12u5_amd64.deb" {
|
||||
t.Errorf("Filename: got %q", p.Filename)
|
||||
}
|
||||
if p.SHA256 != "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" {
|
||||
t.Errorf("SHA256: got %q", p.SHA256)
|
||||
}
|
||||
if p.Size != 314456 {
|
||||
t.Errorf("Size: got %d, want %d", p.Size, 314456)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePackages_multiple(t *testing.T) {
|
||||
input := sampleStanza + "\n\n" + sampleStanza2
|
||||
pkgs, err := ParsePackages([]byte(input))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(pkgs) != 2 {
|
||||
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||
}
|
||||
if pkgs[0].Package != "curl" {
|
||||
t.Errorf("first package: got %q, want curl", pkgs[0].Package)
|
||||
}
|
||||
if pkgs[1].Package != "wget" {
|
||||
t.Errorf("second package: got %q, want wget", pkgs[1].Package)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePackages_empty(t *testing.T) {
|
||||
pkgs, err := ParsePackages([]byte(""))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(pkgs) != 0 {
|
||||
t.Errorf("expected 0 packages, got %d", len(pkgs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePackages_missingPackageField(t *testing.T) {
|
||||
bad := `Version: 1.0
|
||||
Architecture: amd64
|
||||
Filename: pool/main/x/x/x_1.0_amd64.deb
|
||||
SHA256: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
Size: 100`
|
||||
_, err := ParsePackages([]byte(bad))
|
||||
if err == nil {
|
||||
t.Error("expected error for missing Package field, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterPackages(t *testing.T) {
|
||||
pkgs, _ := ParsePackages([]byte(sampleStanza + "\n\n" + sampleStanza2))
|
||||
|
||||
// Only curl is "on disk".
|
||||
onDisk := map[string]bool{
|
||||
"pool/main/c/curl/curl_7.88.1-10+deb12u5_amd64.deb": true,
|
||||
}
|
||||
filtered := FilterPackages(pkgs, func(filename string) bool {
|
||||
return onDisk[filename]
|
||||
})
|
||||
|
||||
if len(filtered) != 1 {
|
||||
t.Fatalf("expected 1 filtered package, got %d", len(filtered))
|
||||
}
|
||||
if filtered[0].Package != "curl" {
|
||||
t.Errorf("expected curl, got %q", filtered[0].Package)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEmitRoundTrip(t *testing.T) {
|
||||
input := []byte(sampleStanza + "\n\n" + sampleStanza2 + "\n")
|
||||
pkgs, err := ParsePackages(input)
|
||||
if err != nil {
|
||||
t.Fatalf("parse: %v", err)
|
||||
}
|
||||
|
||||
emitted := emitPackages(pkgs)
|
||||
|
||||
reparsed, err := ParsePackages(emitted)
|
||||
if err != nil {
|
||||
t.Fatalf("reparse: %v", err)
|
||||
}
|
||||
if len(reparsed) != len(pkgs) {
|
||||
t.Fatalf("round-trip length: got %d, want %d", len(reparsed), len(pkgs))
|
||||
}
|
||||
for i := range pkgs {
|
||||
if reparsed[i].Package != pkgs[i].Package {
|
||||
t.Errorf("[%d] Package: got %q, want %q", i, reparsed[i].Package, pkgs[i].Package)
|
||||
}
|
||||
if reparsed[i].Filename != pkgs[i].Filename {
|
||||
t.Errorf("[%d] Filename: got %q, want %q", i, reparsed[i].Filename, pkgs[i].Filename)
|
||||
}
|
||||
if reparsed[i].SHA256 != pkgs[i].SHA256 {
|
||||
t.Errorf("[%d] SHA256: got %q, want %q", i, reparsed[i].SHA256, pkgs[i].SHA256)
|
||||
}
|
||||
if reparsed[i].Size != pkgs[i].Size {
|
||||
t.Errorf("[%d] Size: got %d, want %d", i, reparsed[i].Size, pkgs[i].Size)
|
||||
}
|
||||
}
|
||||
}
|
||||
126
internal/clone/apt/release_test.go
Normal file
126
internal/clone/apt/release_test.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package apt
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const plainRelease = `Origin: Debian
|
||||
Suite: bookworm
|
||||
Codename: bookworm
|
||||
Components: main contrib
|
||||
Architectures: amd64 arm64
|
||||
SHA256:
|
||||
abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 12345 main/binary-amd64/Packages
|
||||
0000111122223333000011112222333300001111222233330000111122223333 67890 main/binary-amd64/Packages.gz
|
||||
`
|
||||
|
||||
const pgpArmoredRelease = `-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA512
|
||||
|
||||
Origin: Debian
|
||||
Suite: bookworm
|
||||
Codename: bookworm
|
||||
Components: main contrib
|
||||
Architectures: amd64
|
||||
SHA256:
|
||||
abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890 12345 main/binary-amd64/Packages
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCgAdFiEE2F...fake-signature...
|
||||
-----END PGP SIGNATURE-----
|
||||
`
|
||||
|
||||
func TestParseRelease_plain(t *testing.T) {
|
||||
rf, err := ParseRelease([]byte(plainRelease))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if rf.Suite != "bookworm" {
|
||||
t.Errorf("Suite: got %q, want bookworm", rf.Suite)
|
||||
}
|
||||
if rf.Codename != "bookworm" {
|
||||
t.Errorf("Codename: got %q, want bookworm", rf.Codename)
|
||||
}
|
||||
if rf.Components != "main contrib" {
|
||||
t.Errorf("Components: got %q, want %q", rf.Components, "main contrib")
|
||||
}
|
||||
if rf.Architectures != "amd64 arm64" {
|
||||
t.Errorf("Architectures: got %q, want %q", rf.Architectures, "amd64 arm64")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRelease_sha256entries(t *testing.T) {
|
||||
rf, err := ParseRelease([]byte(plainRelease))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(rf.SHA256) != 2 {
|
||||
t.Fatalf("SHA256 entries: got %d, want 2", len(rf.SHA256))
|
||||
}
|
||||
e := rf.SHA256[0]
|
||||
if e.Hash != "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" {
|
||||
t.Errorf("entry[0] Hash: got %q", e.Hash)
|
||||
}
|
||||
if e.Size != 12345 {
|
||||
t.Errorf("entry[0] Size: got %d, want 12345", e.Size)
|
||||
}
|
||||
if e.Path != "main/binary-amd64/Packages" {
|
||||
t.Errorf("entry[0] Path: got %q", e.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRelease_pgpArmored(t *testing.T) {
|
||||
rf, err := ParseRelease([]byte(pgpArmoredRelease))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if rf.Suite != "bookworm" {
|
||||
t.Errorf("Suite: got %q, want bookworm", rf.Suite)
|
||||
}
|
||||
if len(rf.SHA256) != 1 {
|
||||
t.Errorf("SHA256 entries: got %d, want 1", len(rf.SHA256))
|
||||
}
|
||||
// PGP signature must not bleed into entries.
|
||||
for _, e := range rf.SHA256 {
|
||||
if strings.Contains(e.Path, "PGP") || strings.Contains(e.Hash, "PGP") {
|
||||
t.Errorf("PGP artifact in entry: %+v", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRelease_parseable(t *testing.T) {
|
||||
entries := []ReleaseEntry{
|
||||
{Hash: "aabbccdd", Size: 100, Path: "main/binary-amd64/Packages"},
|
||||
{Hash: "eeff0011", Size: 200, Path: "main/binary-amd64/Packages.gz"},
|
||||
}
|
||||
data := GenerateRelease("bookworm", "bookworm", []string{"main", "contrib"}, []string{"amd64"}, entries)
|
||||
|
||||
rf, err := ParseRelease(data)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseRelease on generated output: %v", err)
|
||||
}
|
||||
if rf.Suite != "bookworm" {
|
||||
t.Errorf("Suite: got %q, want bookworm", rf.Suite)
|
||||
}
|
||||
if len(rf.SHA256) != 2 {
|
||||
t.Fatalf("SHA256 entries: got %d, want 2", len(rf.SHA256))
|
||||
}
|
||||
if rf.SHA256[0].Size != 100 {
|
||||
t.Errorf("entry[0] Size: got %d, want 100", rf.SHA256[0].Size)
|
||||
}
|
||||
if rf.SHA256[1].Path != "main/binary-amd64/Packages.gz" {
|
||||
t.Errorf("entry[1] Path: got %q", rf.SHA256[1].Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateRelease_emptyEntries(t *testing.T) {
|
||||
data := GenerateRelease("bookworm", "bookworm", []string{"main"}, []string{"amd64"}, nil)
|
||||
rf, err := ParseRelease(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(rf.SHA256) != 0 {
|
||||
t.Errorf("expected 0 SHA256 entries, got %d", len(rf.SHA256))
|
||||
}
|
||||
}
|
||||
284
internal/clone/rpm/metadata_gen_test.go
Normal file
284
internal/clone/rpm/metadata_gen_test.go
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
package rpm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// buildPrimaryGz creates a gzipped primary.xml with the given packages.
|
||||
// Each entry is {name, locationHref}.
|
||||
func buildPrimaryGz(t *testing.T, pkgs []struct{ name, href string }) []byte {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
|
||||
fmt.Fprintf(&buf, `<metadata xmlns="http://linux.duke.edu/metadata/common" packages="%d">`, len(pkgs))
|
||||
for _, p := range pkgs {
|
||||
fmt.Fprintf(&buf,
|
||||
`<package type="rpm"><name>%s</name><location href="%s"/></package>`,
|
||||
p.name, p.href)
|
||||
}
|
||||
buf.WriteString(`</metadata>`)
|
||||
|
||||
var gz bytes.Buffer
|
||||
gzw := gzip.NewWriter(&gz)
|
||||
gzw.Write(buf.Bytes())
|
||||
gzw.Close()
|
||||
return gz.Bytes()
|
||||
}
|
||||
|
||||
// buildRepoMD creates a minimal repomd.xml pointing to the given primary href.
|
||||
func buildRepoMD(t *testing.T, primaryHref string) []byte {
|
||||
t.Helper()
|
||||
repomd := RepoMD{
|
||||
Data: []RepoMDEntry{{
|
||||
Type: "primary",
|
||||
Location: RepoMDLocation{Href: primaryHref},
|
||||
Checksum: RepoMDChecksum{Type: "sha256", Value: "placeholder"},
|
||||
OpenChecksum: RepoMDChecksum{Type: "sha256", Value: "placeholder"},
|
||||
}},
|
||||
}
|
||||
out, err := xml.MarshalIndent(repomd, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("marshal repomd: %v", err)
|
||||
}
|
||||
return append([]byte(xml.Header), out...)
|
||||
}
|
||||
|
||||
// setupRepo creates a local repo dir with a primary.xml.gz, a .primary-source
|
||||
// marker and a repomd.xml. It creates the given rpm files on disk.
|
||||
func setupRepo(t *testing.T, pkgs []struct{ name, href string }, presentOnDisk []string) string {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
repodata := filepath.Join(dir, "repodata")
|
||||
os.MkdirAll(repodata, 0o755)
|
||||
|
||||
// Upstream primary at a hashed path (mimics real layout).
|
||||
upstreamPrimary := "repodata/upstream-primary.xml.gz"
|
||||
if err := os.WriteFile(filepath.Join(dir, upstreamPrimary), buildPrimaryGz(t, pkgs), 0o644); err != nil {
|
||||
t.Fatalf("write upstream primary: %v", err)
|
||||
}
|
||||
|
||||
// .primary-source marker.
|
||||
if err := os.WriteFile(filepath.Join(repodata, PrimarySourceFile), []byte(upstreamPrimary), 0o644); err != nil {
|
||||
t.Fatalf("write .primary-source: %v", err)
|
||||
}
|
||||
|
||||
// repomd.xml pointing at local primary (which doesn't exist yet — regen will create it).
|
||||
if err := os.WriteFile(filepath.Join(repodata, "repomd.xml"), buildRepoMD(t, localPrimaryHref), 0o644); err != nil {
|
||||
t.Fatalf("write repomd.xml: %v", err)
|
||||
}
|
||||
|
||||
// Create selected rpm files on disk.
|
||||
for _, href := range presentOnDisk {
|
||||
dest := filepath.Join(dir, filepath.FromSlash(href))
|
||||
os.MkdirAll(filepath.Dir(dest), 0o755)
|
||||
if err := os.WriteFile(dest, []byte("fake-rpm"), 0o644); err != nil {
|
||||
t.Fatalf("write rpm %s: %v", href, err)
|
||||
}
|
||||
}
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
// parsePrimaryGz decompresses and parses a primary.xml.gz, returning package hrefs.
|
||||
func parsePrimaryGz(t *testing.T, path string) []string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read primary.xml.gz: %v", err)
|
||||
}
|
||||
gz, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatalf("open gzip: %v", err)
|
||||
}
|
||||
defer gz.Close()
|
||||
var meta filterableMetadata
|
||||
if err := xml.NewDecoder(gz).Decode(&meta); err != nil {
|
||||
t.Fatalf("parse primary.xml: %v", err)
|
||||
}
|
||||
hrefs := make([]string, len(meta.Packages))
|
||||
for i, p := range meta.Packages {
|
||||
hrefs[i] = p.Location.Href
|
||||
}
|
||||
return hrefs
|
||||
}
|
||||
|
||||
// --- InitEmptyRepo ---
|
||||
|
||||
func TestInitEmptyRepo_createsStructure(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := InitEmptyRepo(dir); err != nil {
|
||||
t.Fatalf("InitEmptyRepo: %v", err)
|
||||
}
|
||||
|
||||
for _, path := range []string{
|
||||
filepath.Join(dir, "repodata"),
|
||||
filepath.Join(dir, "Packages"),
|
||||
filepath.Join(dir, "repodata", "repomd.xml"),
|
||||
filepath.Join(dir, localPrimaryHref),
|
||||
} {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Errorf("expected %s to exist: %v", path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitEmptyRepo_repomdIsValid(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
InitEmptyRepo(dir)
|
||||
|
||||
data, _ := os.ReadFile(filepath.Join(dir, "repodata", "repomd.xml"))
|
||||
var repomd RepoMD
|
||||
if err := xml.Unmarshal(data, &repomd); err != nil {
|
||||
t.Fatalf("repomd.xml is not valid XML: %v", err)
|
||||
}
|
||||
if len(repomd.Data) == 0 {
|
||||
t.Error("expected at least one entry in repomd.xml")
|
||||
}
|
||||
if repomd.Data[0].Type != "primary" {
|
||||
t.Errorf("first entry type: got %q, want primary", repomd.Data[0].Type)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitEmptyRepo_primaryIsEmpty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
InitEmptyRepo(dir)
|
||||
|
||||
hrefs := parsePrimaryGz(t, filepath.Join(dir, localPrimaryHref))
|
||||
if len(hrefs) != 0 {
|
||||
t.Errorf("expected 0 packages in empty repo, got %d", len(hrefs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitEmptyRepo_idempotent(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
InitEmptyRepo(dir)
|
||||
|
||||
repomdBefore, _ := os.ReadFile(filepath.Join(dir, "repodata", "repomd.xml"))
|
||||
InitEmptyRepo(dir) // second call
|
||||
repomdAfter, _ := os.ReadFile(filepath.Join(dir, "repodata", "repomd.xml"))
|
||||
|
||||
if !bytes.Equal(repomdBefore, repomdAfter) {
|
||||
t.Error("second InitEmptyRepo modified repomd.xml")
|
||||
}
|
||||
}
|
||||
|
||||
// --- RegenerateMetadata ---
|
||||
|
||||
func TestRegenerateMetadata_keepsOnlyPresentFiles(t *testing.T) {
|
||||
allPkgs := []struct{ name, href string }{
|
||||
{"curl", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
{"wget", "Packages/wget-1.21-1.x86_64.rpm"},
|
||||
{"vim", "Packages/vim-9.0-1.x86_64.rpm"},
|
||||
}
|
||||
// Only curl and vim are on disk.
|
||||
presentOnDisk := []string{"Packages/curl-7.88-1.x86_64.rpm", "Packages/vim-9.0-1.x86_64.rpm"}
|
||||
|
||||
dir := setupRepo(t, allPkgs, presentOnDisk)
|
||||
|
||||
if err := RegenerateMetadata(dir); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
|
||||
hrefs := parsePrimaryGz(t, filepath.Join(dir, localPrimaryHref))
|
||||
if len(hrefs) != 2 {
|
||||
t.Fatalf("expected 2 packages, got %d: %v", len(hrefs), hrefs)
|
||||
}
|
||||
hrefSet := map[string]bool{hrefs[0]: true, hrefs[1]: true}
|
||||
if !hrefSet["Packages/curl-7.88-1.x86_64.rpm"] {
|
||||
t.Error("expected curl in output")
|
||||
}
|
||||
if !hrefSet["Packages/vim-9.0-1.x86_64.rpm"] {
|
||||
t.Error("expected vim in output")
|
||||
}
|
||||
if hrefSet["Packages/wget-1.21-1.x86_64.rpm"] {
|
||||
t.Error("wget should have been filtered out (not on disk)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_allAbsent(t *testing.T) {
|
||||
allPkgs := []struct{ name, href string }{
|
||||
{"curl", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
{"wget", "Packages/wget-1.21-1.x86_64.rpm"},
|
||||
}
|
||||
dir := setupRepo(t, allPkgs, nil) // nothing on disk
|
||||
|
||||
if err := RegenerateMetadata(dir); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
|
||||
hrefs := parsePrimaryGz(t, filepath.Join(dir, localPrimaryHref))
|
||||
if len(hrefs) != 0 {
|
||||
t.Errorf("expected 0 packages, got %d", len(hrefs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_updatesRepoMD(t *testing.T) {
|
||||
allPkgs := []struct{ name, href string }{
|
||||
{"curl", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
}
|
||||
dir := setupRepo(t, allPkgs, []string{"Packages/curl-7.88-1.x86_64.rpm"})
|
||||
|
||||
if err := RegenerateMetadata(dir); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
|
||||
data, _ := os.ReadFile(filepath.Join(dir, "repodata", "repomd.xml"))
|
||||
var repomd RepoMD
|
||||
xml.Unmarshal(data, &repomd)
|
||||
|
||||
var primary *RepoMDEntry
|
||||
for i := range repomd.Data {
|
||||
if repomd.Data[i].Type == "primary" {
|
||||
primary = &repomd.Data[i]
|
||||
}
|
||||
}
|
||||
if primary == nil {
|
||||
t.Fatal("no primary entry in repomd.xml after regen")
|
||||
}
|
||||
if primary.Location.Href != localPrimaryHref {
|
||||
t.Errorf("Location.Href: got %q, want %q", primary.Location.Href, localPrimaryHref)
|
||||
}
|
||||
if primary.Checksum.Type != "sha256" {
|
||||
t.Errorf("Checksum.Type: got %q, want sha256", primary.Checksum.Type)
|
||||
}
|
||||
if primary.Checksum.Value == "placeholder" {
|
||||
t.Error("checksum was not updated from placeholder")
|
||||
}
|
||||
if primary.Size == 0 {
|
||||
t.Error("Size should be non-zero after regen")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMetadata_alwaysFiltersFromUpstream(t *testing.T) {
|
||||
// Simulates two successive approvals — regen must always read upstream, not the
|
||||
// previously-filtered local primary.
|
||||
allPkgs := []struct{ name, href string }{
|
||||
{"curl", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
{"wget", "Packages/wget-1.21-1.x86_64.rpm"},
|
||||
{"vim", "Packages/vim-9.0-1.x86_64.rpm"},
|
||||
}
|
||||
dir := setupRepo(t, allPkgs, []string{"Packages/curl-7.88-1.x86_64.rpm"})
|
||||
|
||||
// First regen: only curl on disk.
|
||||
RegenerateMetadata(dir)
|
||||
hrefs := parsePrimaryGz(t, filepath.Join(dir, localPrimaryHref))
|
||||
if len(hrefs) != 1 {
|
||||
t.Fatalf("after first regen: expected 1, got %d", len(hrefs))
|
||||
}
|
||||
|
||||
// Add wget to disk, regen again — vim still absent.
|
||||
dest := filepath.Join(dir, "Packages", "wget-1.21-1.x86_64.rpm")
|
||||
os.WriteFile(dest, []byte("fake"), 0o644)
|
||||
|
||||
RegenerateMetadata(dir)
|
||||
hrefs = parsePrimaryGz(t, filepath.Join(dir, localPrimaryHref))
|
||||
if len(hrefs) != 2 {
|
||||
t.Fatalf("after second regen: expected 2, got %d: %v", len(hrefs), hrefs)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue