test(legacy): add ExportLegacy drop and translation tests

Covers all cases where canonical data can't be expressed in the legacy
Node.js cache format, with explicit verification:

- Variant builds dropped and counted (Node.js has no variant logic)
- Unknown formats dropped and counted (.apk, .AppImage, .deb, .rpm)
- Empty format passes through (bare binaries, git sources)
- solaris/illumos translated to sunos (Node.js only knows sunos)
- ffmpeg windows .gz overridden to .exe (per-package compat rule)
- ffmpeg translation not applied to other packages
- universal2 passes through as-is (Node.js WATERFALL handles it)
- Mixed: correct counts when drops and translations occur together
This commit is contained in:
AJ ONeal
2026-03-11 16:23:58 -06:00
parent 583afcd7e5
commit bb6a91b709

View File

@@ -7,6 +7,205 @@ import (
"github.com/webinstall/webi-installers/internal/storage"
)
// TestExportLegacyDrops verifies that ExportLegacy correctly drops and counts
// assets that can't be represented in the Node.js legacy cache format.
func TestExportLegacyDrops(t *testing.T) {
t.Run("variant_builds_dropped", func(t *testing.T) {
// Assets with variant tags (rocm, installer, fxdependent, etc.) are
// dropped because Node.js has no variant-selection logic.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "ollama-linux-amd64-rocm.tgz", OS: "linux", Arch: "x86_64", Format: ".tar.gz", Variants: []string{"rocm"}},
{Filename: "ollama-linux-amd64.tgz", OS: "linux", Arch: "x86_64", Format: ".tar.gz"},
},
}
lc, stats := storage.ExportLegacy("ollama", pd)
if stats.Variants != 1 {
t.Errorf("Variants dropped = %d, want 1", stats.Variants)
}
if len(lc.Releases) != 1 {
t.Errorf("releases = %d, want 1 (baseline only)", len(lc.Releases))
}
if lc.Releases[0].Name != "ollama-linux-amd64.tgz" {
t.Errorf("kept wrong release: %q", lc.Releases[0].Name)
}
})
t.Run("unknown_formats_dropped", func(t *testing.T) {
// .apk, .AppImage, .deb, .rpm are not in the Node.js format set.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "tool.apk", OS: "android", Format: ".apk"},
{Filename: "tool.AppImage", OS: "linux", Format: ".AppImage"},
{Filename: "tool.deb", OS: "linux", Format: ".deb"},
{Filename: "tool.rpm", OS: "linux", Format: ".rpm"},
{Filename: "tool-linux-amd64.tar.gz", OS: "linux", Arch: "x86_64", Format: ".tar.gz"},
},
}
lc, stats := storage.ExportLegacy("tool", pd)
if stats.Formats != 4 {
t.Errorf("Formats dropped = %d, want 4", stats.Formats)
}
if len(lc.Releases) != 1 {
t.Errorf("releases = %d, want 1 (tar.gz only)", len(lc.Releases))
}
})
t.Run("empty_format_passes_through", func(t *testing.T) {
// Assets with empty format (e.g. bare binaries, git sources) pass through.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "jq-linux-amd64", OS: "linux", Arch: "x86_64", Format: ""},
},
}
lc, stats := storage.ExportLegacy("jq", pd)
if stats.Formats != 0 {
t.Errorf("Formats dropped = %d, want 0", stats.Formats)
}
if len(lc.Releases) != 1 {
t.Errorf("releases = %d, want 1", len(lc.Releases))
}
})
}
// TestExportLegacyTranslations verifies that legacyFieldBackport applies the
// correct field translations for Node.js compatibility. Translations are lossy
// in canonical terms but necessary for the legacy resolver.
func TestExportLegacyTranslations(t *testing.T) {
t.Run("solaris_translated_to_sunos", func(t *testing.T) {
// Node.js only knows "sunos" for Sun/Oracle platforms.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "go1.20.1.solaris-amd64.tar.gz", OS: "solaris", Arch: "x86_64", Format: ".tar.gz"},
},
}
lc, _ := storage.ExportLegacy("go", pd)
if len(lc.Releases) != 1 {
t.Fatalf("releases = %d, want 1", len(lc.Releases))
}
if lc.Releases[0].OS != "sunos" {
t.Errorf("OS = %q, want %q", lc.Releases[0].OS, "sunos")
}
})
t.Run("illumos_translated_to_sunos", func(t *testing.T) {
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "caddy_2.9.0_illumos_amd64.tar.gz", OS: "illumos", Arch: "x86_64", Format: ".tar.gz"},
},
}
lc, _ := storage.ExportLegacy("caddy", pd)
if len(lc.Releases) != 1 {
t.Fatalf("releases = %d, want 1", len(lc.Releases))
}
if lc.Releases[0].OS != "sunos" {
t.Errorf("OS = %q, want %q", lc.Releases[0].OS, "sunos")
}
})
t.Run("ffmpeg_windows_gz_to_exe", func(t *testing.T) {
// ffmpeg Windows releases are .gz archives containing a bare .exe.
// Production releases.js overrides ext to 'exe' for install compatibility.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "ffmpeg-7.0-windows-amd64.gz", OS: "windows", Arch: "x86_64", Format: ".gz"},
{Filename: "ffmpeg-7.0-linux-amd64.tar.gz", OS: "linux", Arch: "x86_64", Format: ".tar.gz"},
},
}
lc, _ := storage.ExportLegacy("ffmpeg", pd)
if len(lc.Releases) != 2 {
t.Fatalf("releases = %d, want 2", len(lc.Releases))
}
var windowsExt string
for _, r := range lc.Releases {
if r.OS == "windows" {
windowsExt = r.Ext
}
}
if windowsExt != ".exe" {
t.Errorf("ffmpeg windows ext = %q, want %q", windowsExt, ".exe")
}
})
t.Run("ffmpeg_translation_not_applied_to_other_packages", func(t *testing.T) {
// The ffmpeg .gz→.exe translation is package-specific.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "othertool-windows-amd64.gz", OS: "windows", Arch: "x86_64", Format: ".gz"},
},
}
lc, _ := storage.ExportLegacy("othertool", pd)
if len(lc.Releases) != 1 {
t.Fatalf("releases = %d, want 1", len(lc.Releases))
}
if lc.Releases[0].Ext != ".gz" {
t.Errorf("ext = %q, want %q (no translation outside ffmpeg)", lc.Releases[0].Ext, ".gz")
}
})
t.Run("universal2_passes_through", func(t *testing.T) {
// universal2 (x86_64 + ARM64 fat binary) is kept as-is in the legacy
// cache. The Node.js side handles resolution via its WATERFALL/CompatArches.
pd := storage.PackageData{
Assets: []storage.Asset{
{Filename: "hugo_0.145.0_darwin-universal.tar.gz", OS: "darwin", Arch: "universal2", Format: ".tar.gz"},
},
}
lc, stats := storage.ExportLegacy("hugo", pd)
if stats.Variants != 0 || stats.Formats != 0 {
t.Errorf("unexpected drops: variants=%d formats=%d", stats.Variants, stats.Formats)
}
if len(lc.Releases) != 1 {
t.Fatalf("releases = %d, want 1", len(lc.Releases))
}
if lc.Releases[0].Arch != "universal2" {
t.Errorf("arch = %q, want %q", lc.Releases[0].Arch, "universal2")
}
})
}
// TestExportLegacyMixed verifies correct counting when multiple drop/translate
// categories appear together in a single export call.
func TestExportLegacyMixed(t *testing.T) {
pd := storage.PackageData{
Assets: []storage.Asset{
// kept: baseline linux build
{Filename: "tool-linux-amd64.tar.gz", OS: "linux", Arch: "x86_64", Format: ".tar.gz"},
// dropped: variant build
{Filename: "tool-linux-amd64-rocm.tar.gz", OS: "linux", Arch: "x86_64", Format: ".tar.gz", Variants: []string{"rocm"}},
// dropped: .apk format
{Filename: "tool.apk", OS: "android", Format: ".apk"},
// kept: illumos (translated to sunos)
{Filename: "tool-illumos-amd64.tar.gz", OS: "illumos", Arch: "x86_64", Format: ".tar.gz"},
},
}
lc, stats := storage.ExportLegacy("tool", pd)
if stats.Variants != 1 {
t.Errorf("Variants = %d, want 1", stats.Variants)
}
if stats.Formats != 1 {
t.Errorf("Formats = %d, want 1", stats.Formats)
}
if len(lc.Releases) != 2 {
t.Errorf("releases = %d, want 2 (linux + sunos)", len(lc.Releases))
}
// Verify the illumos → sunos translation was applied.
var foundSunos bool
for _, r := range lc.Releases {
if r.OS == "sunos" {
foundSunos = true
}
if r.OS == "illumos" {
t.Error("illumos should have been translated to sunos")
}
}
if !foundSunos {
t.Error("expected a sunos release (translated from illumos)")
}
}
// TestDecodeLegacyJSON verifies we can parse the exact JSON format
// the Node.js server writes to _cache/.
func TestDecodeLegacyJSON(t *testing.T) {