package updater import ( "context" "net/http" "net/http/httptest" "strings" "testing" ) const sampleManifest = `{ "schema_version": 1, "latest_stable": "v0.1.1", "releases": [ { "version": "v0.1.0", "tarball_url": "https://releases.thaloco.com/banger/v0.1.0/banger-v0.1.0-linux-amd64.tar.gz", "sha256sums_url": "https://releases.thaloco.com/banger/v0.1.0/SHA256SUMS", "sha256sums_sig_url": "https://releases.thaloco.com/banger/v0.1.0/SHA256SUMS.sig", "released_at": "2026-04-29T10:00:00Z" }, { "version": "v0.1.1", "tarball_url": "https://releases.thaloco.com/banger/v0.1.1/banger-v0.1.1-linux-amd64.tar.gz", "sha256sums_url": "https://releases.thaloco.com/banger/v0.1.1/SHA256SUMS", "sha256sums_sig_url": "https://releases.thaloco.com/banger/v0.1.1/SHA256SUMS.sig", "released_at": "2026-05-01T10:00:00Z" } ] }` func TestParseManifestHappyPath(t *testing.T) { m, err := ParseManifest([]byte(sampleManifest)) if err != nil { t.Fatalf("ParseManifest: %v", err) } if m.LatestStable != "v0.1.1" || len(m.Releases) != 2 { t.Fatalf("manifest = %+v, want 2 releases with latest_stable=v0.1.1", m) } } func TestParseManifestRejectsNewerSchema(t *testing.T) { body := strings.Replace(sampleManifest, `"schema_version": 1`, `"schema_version": 99`, 1) _, err := ParseManifest([]byte(body)) if err == nil || !strings.Contains(err.Error(), "newer than this CLI") { t.Fatalf("err = %v, want newer-schema rejection", err) } } func TestParseManifestRejectsMissingFields(t *testing.T) { for _, tc := range []struct { name string body string }{ {name: "missing_schema_version", body: `{"latest_stable":"v0.1.0","releases":[]}`}, {name: "missing_tarball_url", body: `{"schema_version":1,"latest_stable":"v0.1.0","releases":[{"version":"v0.1.0","sha256sums_url":"x"}]}`}, {name: "missing_sha256sums_url", body: `{"schema_version":1,"latest_stable":"v0.1.0","releases":[{"version":"v0.1.0","tarball_url":"x"}]}`}, {name: "empty_version", body: `{"schema_version":1,"latest_stable":"v0.1.0","releases":[{"tarball_url":"x","sha256sums_url":"y"}]}`}, {name: "garbage", body: "not json"}, } { tc := tc t.Run(tc.name, func(t *testing.T) { if _, err := ParseManifest([]byte(tc.body)); err == nil { t.Fatalf("expected error parsing %s; got success", tc.name) } }) } } func TestManifestLookupRelease(t *testing.T) { m, _ := ParseManifest([]byte(sampleManifest)) r, err := m.LookupRelease("v0.1.0") if err != nil { t.Fatalf("LookupRelease(v0.1.0): %v", err) } if !strings.HasSuffix(r.TarballURL, "banger-v0.1.0-linux-amd64.tar.gz") { t.Fatalf("wrong tarball url: %s", r.TarballURL) } if _, err := m.LookupRelease("v9.9.9"); err == nil { t.Fatal("expected error looking up missing release") } } func TestManifestLatest(t *testing.T) { m, _ := ParseManifest([]byte(sampleManifest)) r, err := m.Latest() if err != nil { t.Fatalf("Latest: %v", err) } if r.Version != "v0.1.1" { t.Fatalf("Latest.Version = %s, want v0.1.1", r.Version) } } func TestFetchManifestRoundTrip(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(sampleManifest)) })) defer srv.Close() // Drive FetchManifest by overriding the global URL temporarily. prev := manifestURL manifestURL = srv.URL defer func() { manifestURL = prev }() m, err := FetchManifest(context.Background(), srv.Client()) if err != nil { t.Fatalf("FetchManifest: %v", err) } if m.LatestStable != "v0.1.1" { t.Fatalf("LatestStable = %s", m.LatestStable) } }