a73x

internal/site/manifest_test.go

Ref:   Size: 6.4 KiB   History

package site

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/a73x/eitri/internal/server/release"
)

func TestBuildManifestFromHostBundles(t *testing.T) {
	dist := t.TempDir()
	body := []byte("fake host bundle")
	for _, name := range []string{
		"eitri-server_v0.0.1_linux_amd64.tar.gz",
		"eitri-server_v0.0.1_linux_arm64.tar.gz",
	} {
		if err := os.WriteFile(filepath.Join(dist, name), body, 0o644); err != nil {
			t.Fatal(err)
		}
	}
	// The bundles that carry no agent stay out of the manifest: an agent
	// pointed at one would swap itself for a binary that is not an agent.
	for _, name := range []string{
		"eitri-cli_v0.0.1_linux_amd64.tar.gz",
		"eitri-oidc_v0.0.1_linux_amd64.tar.gz",
	} {
		if err := os.WriteFile(filepath.Join(dist, name), body, 0o644); err != nil {
			t.Fatal(err)
		}
	}

	m, err := BuildManifest("v0.0.1", dist, "https://eitri.sh/dl/v0.0.1")
	if err != nil {
		t.Fatal(err)
	}
	if m.Version != "v0.0.1" {
		t.Errorf("version = %q", m.Version)
	}
	agents := m.Artifacts["eitri-agent"]
	if len(agents) != 2 {
		t.Fatalf("want 2 platforms, got %v", agents)
	}
	sum := sha256.Sum256(body)
	want := release.Artifact{
		URL:    "https://eitri.sh/dl/v0.0.1/eitri-server_v0.0.1_linux_amd64.tar.gz",
		SHA256: hex.EncodeToString(sum[:]),
	}
	if agents["linux/amd64"] != want {
		t.Errorf("linux/amd64 = %+v, want %+v", agents["linux/amd64"], want)
	}

	// Round-trip: what we emit is exactly what the server-side type parses.
	raw, err := json.Marshal(m)
	if err != nil {
		t.Fatal(err)
	}
	var back release.Manifest
	if err := json.Unmarshal(raw, &back); err != nil {
		t.Fatal(err)
	}
	if back.Artifacts["eitri-agent"]["linux/arm64"].SHA256 != agents["linux/arm64"].SHA256 {
		t.Error("round-trip through release.Manifest lost data")
	}
}

func TestBuildManifestRequiresHostBundles(t *testing.T) {
	if _, err := BuildManifest("v0.0.1", t.TempDir(), "https://eitri.sh/dl/v0.0.1"); err == nil {
		t.Fatal("want error when no host bundles exist")
	}
}

func TestBuildManifestRequiresVersion(t *testing.T) {
	if _, err := BuildManifest("", t.TempDir(), "https://eitri.sh/dl/v0.0.1"); err == nil {
		t.Fatal("want error for empty version")
	}
}

func TestBuildManifestNormalizesBaseURL(t *testing.T) {
	dist := t.TempDir()
	if err := os.WriteFile(filepath.Join(dist, "eitri-server_v0.0.1_linux_amd64.tar.gz"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}
	m, err := BuildManifest("v0.0.1", dist, "https://eitri.sh/dl/v0.0.1/")
	if err != nil {
		t.Fatal(err)
	}
	got := m.Artifacts["eitri-agent"]["linux/amd64"].URL
	if got != "https://eitri.sh/dl/v0.0.1/eitri-server_v0.0.1_linux_amd64.tar.gz" {
		t.Errorf("trailing-slash base not normalized: %q", got)
	}
}

func TestBuildManifestIncludesRuntimeArtifacts(t *testing.T) {
	dist := t.TempDir()
	bundle := []byte("fake host bundle")
	ch := []byte("fake cloud-hypervisor binary")
	fw := []byte("fake CLOUDHV.fd")
	files := map[string][]byte{
		"eitri-server_v0.0.1_linux_amd64.tar.gz": bundle,
		"eitri-server_v0.0.1_linux_arm64.tar.gz": bundle,
		"cloud-hypervisor_linux_amd64":           ch,
		"cloud-hypervisor_linux_arm64":           ch,
		"CLOUDHV.fd":                             fw,
	}
	for name, body := range files {
		if err := os.WriteFile(filepath.Join(dist, name), body, 0o755); err != nil {
			t.Fatal(err)
		}
	}

	m, err := BuildManifest("v0.0.1", dist, "https://eitri.sh/dl/v0.0.1")
	if err != nil {
		t.Fatal(err)
	}

	ch256 := sha256.Sum256(ch)
	chArtifacts := m.Artifacts["cloud-hypervisor"]
	if len(chArtifacts) != 2 {
		t.Fatalf("want 2 cloud-hypervisor platforms, got %v", chArtifacts)
	}
	wantCH := release.Artifact{
		URL:    "https://eitri.sh/dl/v0.0.1/cloud-hypervisor_linux_amd64",
		SHA256: hex.EncodeToString(ch256[:]),
	}
	if chArtifacts["linux/amd64"] != wantCH {
		t.Errorf("cloud-hypervisor linux/amd64 = %+v, want %+v", chArtifacts["linux/amd64"], wantCH)
	}

	fw256 := sha256.Sum256(fw)
	fwArtifacts := m.Artifacts["firmware"]
	if len(fwArtifacts) != 1 {
		t.Fatalf("want 1 firmware platform, got %v", fwArtifacts)
	}
	wantFW := release.Artifact{
		URL:    "https://eitri.sh/dl/v0.0.1/CLOUDHV.fd",
		SHA256: hex.EncodeToString(fw256[:]),
	}
	if fwArtifacts["linux/amd64"] != wantFW {
		t.Errorf("firmware linux/amd64 = %+v, want %+v", fwArtifacts["linux/amd64"], wantFW)
	}
}

func TestBuildManifestRuntimeArtifactsOptional(t *testing.T) {
	dist := t.TempDir()
	if err := os.WriteFile(filepath.Join(dist, "eitri-server_v0.0.1_linux_amd64.tar.gz"), []byte("x"), 0o644); err != nil {
		t.Fatal(err)
	}

	m, err := BuildManifest("v0.0.1", dist, "https://eitri.sh/dl/v0.0.1")
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := m.Artifacts["cloud-hypervisor"]; ok {
		t.Errorf("cloud-hypervisor key present without any cloud-hypervisor binaries: %v", m.Artifacts["cloud-hypervisor"])
	}
	if _, ok := m.Artifacts["firmware"]; ok {
		t.Errorf("firmware key present without CLOUDHV.fd: %v", m.Artifacts["firmware"])
	}
}

// TestBuildManifestCarriesADarwinAgent pins that a Mac host's agent manifests
// like any other. The upgrade handler looks the artifact up by the host row's
// own "<os>/<arch>", so a darwin agent that never reaches the manifest leaves
// every Mac in the fleet permanently unable to upgrade — and says so with a
// 409, which reads like a server fault rather than a missing build.
//
// It also pins the negative: a Mac needs no cloud-hypervisor and no CLOUDHV.fd
// (its runtime is vfkit, which only Apple can sign), so nothing must invent
// darwin entries for those.
func TestBuildManifestCarriesADarwinAgent(t *testing.T) {
	dist := t.TempDir()
	for _, name := range []string{
		"eitri-server_v0.0.3_linux_amd64.tar.gz",
		"eitri-agent_v0.0.3_darwin_arm64.tar.gz",
		"cloud-hypervisor_linux_amd64", "CLOUDHV.fd",
	} {
		if err := os.WriteFile(filepath.Join(dist, name), []byte(name), 0o644); err != nil {
			t.Fatal(err)
		}
	}
	m, err := BuildManifest("v0.0.3", dist, "https://eitri.sh/dl/v0.0.3")
	if err != nil {
		t.Fatal(err)
	}
	art, ok := m.Artifacts["eitri-agent"]["darwin/arm64"]
	if !ok {
		t.Fatal("no eitri-agent artifact for darwin/arm64")
	}
	if want := "https://eitri.sh/dl/v0.0.3/eitri-agent_v0.0.3_darwin_arm64.tar.gz"; art.URL != want {
		t.Errorf("url = %q, want %q", art.URL, want)
	}
	if art.SHA256 == "" {
		t.Error("darwin agent artifact has no digest")
	}
	for _, key := range []string{"cloud-hypervisor", "firmware"} {
		if _, ok := m.Artifacts[key]["darwin/arm64"]; ok {
			t.Errorf("%s must not manifest for darwin — a Mac's runtime is vfkit, which we cannot ship", key)
		}
	}
}