package app import ( "context" "io" "net/http" "strings" "sync/atomic" "testing" ) type updateTransport func(*http.Request) (*http.Response, error) func (transport updateTransport) RoundTrip(request *http.Request) (*http.Response, error) { return transport(request) } func TestStableVersionComparison(t *testing.T) { for _, test := range []struct { current, latest string known, newer bool }{ {"v1.19.9", "v1.19.32", true, true}, {"1.19.32", "v1.19.32", true, false}, {"v2.0.0", "v1.99.99", true, false}, {"v1.19.32", "v1.20.0", true, true}, {"", "v1.19.32", false, false}, {"Meta-alpha", "v1.19.32", false, false}, {"v1.19.32", "v1.20.0-beta", false, false}, } { result := newerStableVersion(test.current, test.latest) if (result != nil) != test.known || result != nil && *result != test.newer { t.Fatalf("%s -> %s: got %v", test.current, test.latest, result) } } } func TestUpdateCheckAuthCachingAndFailure(t *testing.T) { a := newTestApp(t) var calls atomic.Int32 a.download.Transport = updateTransport(func(r *http.Request) (*http.Response, error) { calls.Add(1) if r.URL.String() != coreReleaseEndpoint { t.Fatal("unexpected update source", r.URL) } return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"tag_name":"v1.19.33","published_at":"2026-10-03T00:00:00Z"}`)), Header: http.Header{}}, nil }) if w := request(t, a, "GET", "/api/v1/system/updates", nil, nil, ""); w.Code != 401 || calls.Load() != 0 { t.Fatal("unauthenticated update check must not fetch metadata") } cookie, _ := loginTest(t, a) for range 2 { w := request(t, a, "GET", "/api/v1/system/updates", nil, cookie, "") if w.Code != 200 || !strings.Contains(w.Body.String(), `"updateAvailable":null`) || !strings.Contains(w.Body.String(), `"latestVersion":"v1.19.33"`) { t.Fatal(w.Code, w.Body) } } if calls.Load() != 1 { t.Fatal("repeated checks must use cached release metadata") } a.updateCache = nil a.download.Transport = updateTransport(func(*http.Request) (*http.Response, error) { return &http.Response{StatusCode: 429, Body: io.NopCloser(strings.NewReader("rate limited")), Header: http.Header{}}, nil }) if w := request(t, a, "GET", "/api/v1/system/updates", nil, cookie, ""); w.Code != 502 { t.Fatal(w.Code, w.Body) } } func TestUpdateCheckRejectsInvalidRelease(t *testing.T) { for _, body := range []string{ `{"tag_name":"v1.20.0-beta"}`, `{"tag_name":"v1.20.0","draft":true}`, `{"tag_name":"v1.20.0","prerelease":true}`, `{broken`, strings.Repeat("x", (2<<20)+1), } { client := &http.Client{Transport: updateTransport(func(*http.Request) (*http.Response, error) { return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: http.Header{}}, nil })} if _, err := fetchCoreRelease(context.Background(), client); err == nil { t.Fatal("invalid stable release metadata accepted") } } }