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
417
internal/core/snapshot_test.go
Normal file
417
internal/core/snapshot_test.go
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
package core_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
aptclone "github.com/syonad/clonepack/internal/clone/apt"
|
||||
rpmclone "github.com/syonad/clonepack/internal/clone/rpm"
|
||||
"github.com/syonad/clonepack/internal/store"
|
||||
)
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
// buildRPMRepo creates an RPM repo (repomd.xml + upstream primary.xml.gz +
|
||||
// .primary-source marker) in localDir and writes fake package files on disk.
|
||||
func buildRPMRepo(t *testing.T, localDir string, pkgs []struct{ name, version, href string }) {
|
||||
t.Helper()
|
||||
if err := rpmclone.InitEmptyRepo(localDir); err != nil {
|
||||
t.Fatalf("InitEmptyRepo: %v", err)
|
||||
}
|
||||
|
||||
// Build upstream primary.xml.gz.
|
||||
var xmlBuf bytes.Buffer
|
||||
xmlBuf.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
|
||||
fmt.Fprintf(&xmlBuf, `<metadata xmlns="http://linux.duke.edu/metadata/common" packages="%d">`, len(pkgs))
|
||||
for _, p := range pkgs {
|
||||
fmt.Fprintf(&xmlBuf,
|
||||
`<package type="rpm"><name>%s</name>`+
|
||||
`<version epoch="0" ver="%s" rel="1"/>`+
|
||||
`<arch>x86_64</arch>`+
|
||||
`<checksum type="sha256" pkgid="YES">abc%s</checksum>`+
|
||||
`<size package="1024" installed="2048" archive="1500"/>`+
|
||||
`<location href="%s"/>`+
|
||||
`</package>`,
|
||||
p.name, p.version, p.name, p.href)
|
||||
}
|
||||
xmlBuf.WriteString(`</metadata>`)
|
||||
|
||||
var gz bytes.Buffer
|
||||
gzw := gzip.NewWriter(&gz)
|
||||
gzw.Write(xmlBuf.Bytes())
|
||||
gzw.Close()
|
||||
|
||||
upstreamPath := filepath.Join(localDir, "repodata", "upstream-primary.xml.gz")
|
||||
if err := os.WriteFile(upstreamPath, gz.Bytes(), 0o644); err != nil {
|
||||
t.Fatalf("write upstream primary: %v", err)
|
||||
}
|
||||
|
||||
// .primary-source marker so RegenerateMetadata always filters from upstream.
|
||||
marker := filepath.Join(localDir, "repodata", rpmclone.PrimarySourceFile)
|
||||
if err := os.WriteFile(marker, []byte("repodata/upstream-primary.xml.gz"), 0o644); err != nil {
|
||||
t.Fatalf("write .primary-source: %v", err)
|
||||
}
|
||||
|
||||
// Minimal repomd.xml pointing at the upstream (regen will redirect to local).
|
||||
type loc struct {
|
||||
Href string `xml:"href,attr"`
|
||||
}
|
||||
type chk struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
type entry struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Location loc `xml:"location"`
|
||||
Checksum chk `xml:"checksum"`
|
||||
}
|
||||
type repomdDoc struct {
|
||||
XMLName xml.Name `xml:"repomd"`
|
||||
Data []entry `xml:"data"`
|
||||
}
|
||||
rm := repomdDoc{Data: []entry{{
|
||||
Type: "primary",
|
||||
Location: loc{Href: "repodata/upstream-primary.xml.gz"},
|
||||
Checksum: chk{Type: "sha256", Value: "placeholder"},
|
||||
}}}
|
||||
repomdXML, _ := xml.MarshalIndent(rm, "", " ")
|
||||
if err := os.WriteFile(
|
||||
filepath.Join(localDir, "repodata", "repomd.xml"),
|
||||
append([]byte(xml.Header), repomdXML...),
|
||||
0o644,
|
||||
); err != nil {
|
||||
t.Fatalf("write repomd.xml: %v", err)
|
||||
}
|
||||
|
||||
// Create fake package files.
|
||||
for _, p := range pkgs {
|
||||
dest := filepath.Join(localDir, filepath.FromSlash(p.href))
|
||||
os.MkdirAll(filepath.Dir(dest), 0o755)
|
||||
os.WriteFile(dest, []byte("fake-rpm"), 0o644)
|
||||
}
|
||||
|
||||
// Regenerate so repomd.xml reflects actual checksums.
|
||||
if err := rpmclone.RegenerateMetadata(localDir); err != nil {
|
||||
t.Fatalf("RegenerateMetadata: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// buildAPTRepo creates an APT repo with Packages files and fake deb files on disk.
|
||||
func buildAPTRepo(t *testing.T, localDir string, cfg aptclone.Config, debs []struct{ pkg, version, filename string }) {
|
||||
t.Helper()
|
||||
if err := aptclone.InitEmptyRepo(localDir, cfg); err != nil {
|
||||
t.Fatalf("InitEmptyRepo apt: %v", err)
|
||||
}
|
||||
|
||||
var stanzas []string
|
||||
for _, d := range debs {
|
||||
stanzas = append(stanzas, strings.Join([]string{
|
||||
"Package: " + d.pkg,
|
||||
"Version: " + d.version,
|
||||
"Architecture: amd64",
|
||||
"Filename: " + d.filename,
|
||||
"SHA256: abc" + d.pkg,
|
||||
"Size: 1024",
|
||||
}, "\n"))
|
||||
}
|
||||
|
||||
plain := []byte(strings.Join(stanzas, "\n\n") + "\n")
|
||||
var gz bytes.Buffer
|
||||
gzw := gzip.NewWriter(&gz)
|
||||
gzw.Write(plain)
|
||||
gzw.Close()
|
||||
gzData := gz.Bytes()
|
||||
|
||||
for _, component := range cfg.Components {
|
||||
for _, arch := range cfg.Architectures {
|
||||
binDir := filepath.Join(localDir, "dists", cfg.Suite, component, "binary-"+arch)
|
||||
os.MkdirAll(binDir, 0o755)
|
||||
os.WriteFile(filepath.Join(binDir, ".upstream-Packages.gz"), gzData, 0o644)
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range debs {
|
||||
dest := filepath.Join(localDir, filepath.FromSlash(d.filename))
|
||||
os.MkdirAll(filepath.Dir(dest), 0o755)
|
||||
os.WriteFile(dest, []byte("fake-deb"), 0o644)
|
||||
}
|
||||
|
||||
if err := aptclone.RegenerateMetadata(localDir, cfg); err != nil {
|
||||
t.Fatalf("RegenerateMetadata apt: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- TakeSnapshot RPM ---
|
||||
|
||||
func TestTakeSnapshot_RPM_capturesPackages(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "rpm-repo", Type: "rpm", SourceURL: "https://example.com", SyncMode: "manual",
|
||||
})
|
||||
localDir := filepath.Join(env.dataDir, "repos", "1", "rpm")
|
||||
buildRPMRepo(t, localDir, []struct{ name, version, href string }{
|
||||
{"curl", "7.88", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
{"wget", "1.21", "Packages/wget-1.21-1.x86_64.rpm"},
|
||||
})
|
||||
|
||||
snapID, err := env.snapshotSvc.TakeSnapshot(ctx, repoID, "v1")
|
||||
if err != nil {
|
||||
t.Fatalf("TakeSnapshot: %v", err)
|
||||
}
|
||||
|
||||
pkgs, _ := env.snapshotStore.GetSnapshotPackages(ctx, snapID)
|
||||
if len(pkgs) != 2 {
|
||||
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||
}
|
||||
names := map[string]bool{}
|
||||
for _, p := range pkgs {
|
||||
names[p.Name] = true
|
||||
}
|
||||
if !names["curl"] || !names["wget"] {
|
||||
t.Errorf("unexpected package names: %v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTakeSnapshot_RPM_emptyRepo(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "rpm-empty", Type: "rpm", SourceURL: "https://example.com", SyncMode: "manual",
|
||||
})
|
||||
localDir := filepath.Join(env.dataDir, "repos", "1", "rpm")
|
||||
buildRPMRepo(t, localDir, nil)
|
||||
|
||||
snapID, err := env.snapshotSvc.TakeSnapshot(ctx, repoID, "empty")
|
||||
if err != nil {
|
||||
t.Fatalf("TakeSnapshot: %v", err)
|
||||
}
|
||||
pkgs, _ := env.snapshotStore.GetSnapshotPackages(ctx, snapID)
|
||||
if len(pkgs) != 0 {
|
||||
t.Errorf("expected 0 packages, got %d", len(pkgs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTakeSnapshot_repoNotFound(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
_, err := env.snapshotSvc.TakeSnapshot(context.Background(), 9999, "v1")
|
||||
if err == nil {
|
||||
t.Error("expected error for unknown repo")
|
||||
}
|
||||
}
|
||||
|
||||
// --- TakeSnapshot APT ---
|
||||
|
||||
func TestTakeSnapshot_APT_capturesPackages(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
cfg := aptclone.Config{Suite: "bookworm", Components: []string{"main"}, Architectures: []string{"amd64"}}
|
||||
cfgJSON, _ := json.Marshal(cfg)
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "apt-repo", Type: "apt", SourceURL: "https://deb.debian.org/debian",
|
||||
SyncMode: "manual", Config: string(cfgJSON),
|
||||
})
|
||||
localDir := filepath.Join(env.dataDir, "repos", "1", "apt")
|
||||
buildAPTRepo(t, localDir, cfg, []struct{ pkg, version, filename string }{
|
||||
{"curl", "7.88.1-10", "pool/main/c/curl/curl_7.88.1-10_amd64.deb"},
|
||||
{"vim", "2:9.0.1378-2", "pool/main/v/vim/vim_9.0.1378-2_amd64.deb"},
|
||||
})
|
||||
|
||||
snapID, err := env.snapshotSvc.TakeSnapshot(ctx, repoID, "apt-v1")
|
||||
if err != nil {
|
||||
t.Fatalf("TakeSnapshot APT: %v", err)
|
||||
}
|
||||
pkgs, _ := env.snapshotStore.GetSnapshotPackages(ctx, snapID)
|
||||
if len(pkgs) != 2 {
|
||||
t.Fatalf("expected 2 packages, got %d", len(pkgs))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTakeSnapshot_APT_deduplicatesAcrossComponents(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
cfg := aptclone.Config{
|
||||
Suite: "bookworm", Components: []string{"main", "contrib"}, Architectures: []string{"amd64"},
|
||||
}
|
||||
cfgJSON, _ := json.Marshal(cfg)
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "apt-multi", Type: "apt", SourceURL: "https://deb.debian.org/debian",
|
||||
SyncMode: "manual", Config: string(cfgJSON),
|
||||
})
|
||||
localDir := filepath.Join(env.dataDir, "repos", "1", "apt")
|
||||
aptclone.InitEmptyRepo(localDir, cfg)
|
||||
|
||||
// Same .deb appears in both components.
|
||||
stanza := strings.Join([]string{
|
||||
"Package: shared-pkg",
|
||||
"Version: 1.0",
|
||||
"Architecture: amd64",
|
||||
"Filename: pool/main/s/shared-pkg/shared-pkg_1.0_amd64.deb",
|
||||
"SHA256: abcdef",
|
||||
"Size: 1024",
|
||||
}, "\n") + "\n"
|
||||
|
||||
var gz bytes.Buffer
|
||||
gzw := gzip.NewWriter(&gz)
|
||||
gzw.Write([]byte(stanza))
|
||||
gzw.Close()
|
||||
gzData := gz.Bytes()
|
||||
|
||||
for _, component := range cfg.Components {
|
||||
binDir := filepath.Join(localDir, "dists", cfg.Suite, component, "binary-amd64")
|
||||
os.MkdirAll(binDir, 0o755)
|
||||
os.WriteFile(filepath.Join(binDir, ".upstream-Packages.gz"), gzData, 0o644)
|
||||
}
|
||||
dest := filepath.Join(localDir, "pool/main/s/shared-pkg/shared-pkg_1.0_amd64.deb")
|
||||
os.MkdirAll(filepath.Dir(dest), 0o755)
|
||||
os.WriteFile(dest, []byte("fake-deb"), 0o644)
|
||||
aptclone.RegenerateMetadata(localDir, cfg)
|
||||
|
||||
snapID, err := env.snapshotSvc.TakeSnapshot(ctx, repoID, "dedup-test")
|
||||
if err != nil {
|
||||
t.Fatalf("TakeSnapshot: %v", err)
|
||||
}
|
||||
pkgs, _ := env.snapshotStore.GetSnapshotPackages(ctx, snapID)
|
||||
if len(pkgs) != 1 {
|
||||
t.Errorf("expected 1 deduplicated package, got %d", len(pkgs))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Diff ---
|
||||
|
||||
func TestDiff_addsAndRemoves(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "rpm-repo", Type: "rpm", SourceURL: "https://example.com", SyncMode: "manual",
|
||||
})
|
||||
|
||||
snap1ID, _ := env.snapshotStore.CreateSnapshot(ctx, repoID, "v1")
|
||||
env.snapshotStore.AddSnapshotPackages(ctx, snap1ID, []store.SnapshotPackage{
|
||||
{Name: "curl", Version: "7.88-1", Arch: "x86_64", Location: "Packages/curl.rpm", ChecksumType: "sha256"},
|
||||
{Name: "wget", Version: "1.21-1", Arch: "x86_64", Location: "Packages/wget.rpm", ChecksumType: "sha256"},
|
||||
})
|
||||
|
||||
snap2ID, _ := env.snapshotStore.CreateSnapshot(ctx, repoID, "v2")
|
||||
env.snapshotStore.AddSnapshotPackages(ctx, snap2ID, []store.SnapshotPackage{
|
||||
{Name: "curl", Version: "7.88-1", Arch: "x86_64", Location: "Packages/curl.rpm", ChecksumType: "sha256"},
|
||||
{Name: "vim", Version: "9.0-1", Arch: "x86_64", Location: "Packages/vim.rpm", ChecksumType: "sha256"},
|
||||
})
|
||||
|
||||
diff, err := env.snapshotSvc.Diff(ctx, repoID, snap1ID, snap2ID)
|
||||
if err != nil {
|
||||
t.Fatalf("Diff: %v", err)
|
||||
}
|
||||
if diff.Unchanged != 1 {
|
||||
t.Errorf("Unchanged: got %d, want 1", diff.Unchanged)
|
||||
}
|
||||
if len(diff.Added) != 1 || diff.Added[0].Name != "vim" {
|
||||
t.Errorf("Added: got %v, want [vim]", diff.Added)
|
||||
}
|
||||
if len(diff.Removed) != 1 || diff.Removed[0].Name != "wget" {
|
||||
t.Errorf("Removed: got %v, want [wget]", diff.Removed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiff_wrongRepo(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
id1, _ := env.repoStore.CreateRepo(ctx, &store.Repo{Name: "r1", Type: "rpm", SourceURL: "https://a.com", SyncMode: "manual"})
|
||||
id2, _ := env.repoStore.CreateRepo(ctx, &store.Repo{Name: "r2", Type: "rpm", SourceURL: "https://b.com", SyncMode: "manual"})
|
||||
|
||||
snap1ID, _ := env.snapshotStore.CreateSnapshot(ctx, id1, "s1")
|
||||
snap2ID, _ := env.snapshotStore.CreateSnapshot(ctx, id2, "s2")
|
||||
|
||||
_, err := env.snapshotSvc.Diff(ctx, id1, snap1ID, snap2ID)
|
||||
if err == nil {
|
||||
t.Error("expected error when snapshots belong to different repos")
|
||||
}
|
||||
}
|
||||
|
||||
// --- Rollback ---
|
||||
|
||||
func TestRollback_removesExtraFiles(t *testing.T) {
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "rpm-repo", Type: "rpm", SourceURL: "https://example.com", SyncMode: "manual",
|
||||
})
|
||||
localDir := filepath.Join(env.dataDir, "repos", "1", "rpm")
|
||||
buildRPMRepo(t, localDir, []struct{ name, version, href string }{
|
||||
{"curl", "7.88", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
{"wget", "1.21", "Packages/wget-1.21-1.x86_64.rpm"},
|
||||
})
|
||||
|
||||
// Snapshot records only curl.
|
||||
snapID, _ := env.snapshotStore.CreateSnapshot(ctx, repoID, "curl-only")
|
||||
env.snapshotStore.AddSnapshotPackages(ctx, snapID, []store.SnapshotPackage{
|
||||
{Name: "curl", Version: "7.88-1", Arch: "x86_64",
|
||||
Location: "Packages/curl-7.88-1.x86_64.rpm", ChecksumType: "sha256"},
|
||||
})
|
||||
|
||||
curlPath := filepath.Join(localDir, "Packages", "curl-7.88-1.x86_64.rpm")
|
||||
wgetPath := filepath.Join(localDir, "Packages", "wget-1.21-1.x86_64.rpm")
|
||||
|
||||
if err := env.snapshotSvc.Rollback(ctx, repoID, snapID); err != nil {
|
||||
t.Fatalf("Rollback: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(wgetPath); err == nil {
|
||||
t.Error("wget should have been removed by rollback")
|
||||
}
|
||||
if _, err := os.Stat(curlPath); err != nil {
|
||||
t.Errorf("curl should still be present: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollback_redownloadsMissingFiles(t *testing.T) {
|
||||
const fileContent = "fake-rpm-bytes"
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(fileContent))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
env := newTestEnv(t)
|
||||
ctx := context.Background()
|
||||
|
||||
repoID, _ := env.repoStore.CreateRepo(ctx, &store.Repo{
|
||||
Name: "rpm-repo", Type: "rpm", SourceURL: srv.URL, SyncMode: "manual",
|
||||
})
|
||||
localDir := filepath.Join(env.dataDir, "repos", "1", "rpm")
|
||||
buildRPMRepo(t, localDir, []struct{ name, version, href string }{
|
||||
{"curl", "7.88", "Packages/curl-7.88-1.x86_64.rpm"},
|
||||
})
|
||||
|
||||
snapID, _ := env.snapshotStore.CreateSnapshot(ctx, repoID, "v1")
|
||||
env.snapshotStore.AddSnapshotPackages(ctx, snapID, []store.SnapshotPackage{
|
||||
{Name: "curl", Version: "7.88-1", Arch: "x86_64",
|
||||
Location: "Packages/curl-7.88-1.x86_64.rpm", ChecksumType: "sha256"},
|
||||
})
|
||||
|
||||
curlPath := filepath.Join(localDir, "Packages", "curl-7.88-1.x86_64.rpm")
|
||||
os.Remove(curlPath)
|
||||
|
||||
if err := env.snapshotSvc.Rollback(ctx, repoID, snapID); err != nil {
|
||||
t.Fatalf("Rollback: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(curlPath); err != nil {
|
||||
t.Errorf("curl should have been re-downloaded: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue