package web import ( "net/http" "net/http/httptest" "net/url" "strings" "sync/atomic" "testing" "gitea.dooplex.hu/admin/felhom-hub/internal/gitea" ) // R-292: the artifact save names the cause of a checksum failure. One flash used to cover "version // missing", "registry unreachable" and "bad typed sha" — and the operator acts differently on each. // // These drive resolveArtifactSHA through the real handler. The installability gate is passed by a // registry that serves the tag and the download; ONLY the sha metadata route is varied. func shaRegistry(t *testing.T, metaStatus int, metaBody string) *gitea.Client { t.Helper() var hits int32 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { atomic.AddInt32(&hits, 1) p := strings.TrimPrefix(r.URL.Path, "/") if strings.HasPrefix(p, "api/v1/packages/") && strings.HasSuffix(p, "/files") { if metaStatus != http.StatusOK { w.WriteHeader(metaStatus) return } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(metaBody)) return } w.WriteHeader(http.StatusOK) // tag probe + package download: installable })) t.Cleanup(srv.Close) return gitea.New(srv.URL, "admin", "", "") } func TestArtifactSave_FlashNamesTheCause(t *testing.T) { sha := strings.Repeat("c", 64) for _, tc := range []struct { name string metaStatus int metaBody string want string }{ {"version not found", http.StatusNotFound, "", "artifact_version_missing"}, {"registry failing", http.StatusBadGateway, "", "artifact_unverifiable"}, {"no sha listed", http.StatusOK, `[{"name":"x","sha256":""}]`, "artifact_sha_missing"}, {"healthy", http.StatusOK, `[{"name":"x","sha256":"` + sha + `"}]`, "artifacts_set"}, } { t.Run(tc.name, func(t *testing.T) { s, _ := newTestServer(t) s.SetGiteaClient(shaRegistry(t, tc.metaStatus, tc.metaBody)) if got := flashOf(saveArtifacts(t, s)); got != tc.want { t.Fatalf("flash = %q, want %q", got, tc.want) } }) } } // The manual path (no Gitea client): a malformed typed sha is the ONE case artifact_sha_invalid means. func TestArtifactSave_BadTypedSHAIsShaInvalid(t *testing.T) { s, st := newTestServer(t) form := url.Values{"agent_version": {"0.128.0"}, "agent_sha256": {"not-a-sha"}} r := httptest.NewRequest(http.MethodPost, "/configuration/artifacts", strings.NewReader(form.Encode())) r.Header.Set("Content-Type", "application/x-www-form-urlencoded") w := httptest.NewRecorder() s.handleSetArtifacts(w, r) if got := flashOf(w); got != "artifact_sha_invalid" { t.Fatalf("flash = %q, want artifact_sha_invalid", got) } assertUnchanged(t, st, "bad typed sha") } // Every flash the save can redirect to renders its own message (seam: a flash with no template branch // shows the operator nothing). The sha_invalid text no longer claims a lookup failure. func TestArtifactFlashes_EachRendersItsOwnText(t *testing.T) { s, _ := newRenderServer(t) for flash, want := range map[string]string{ "artifact_version_missing": "the registry has no such version", "artifact_sha_missing": "no checksum for a chosen version", "artifact_unverifiable": "could not verify", "artifact_sha_invalid": "a checksum you typed is not valid", } { req := httptest.NewRequest(http.MethodGet, "/configuration?flash="+flash, nil) rr := httptest.NewRecorder() s.handleConfiguration(rr, req) body := rr.Body.String() if !strings.Contains(body, want) { t.Errorf("flash %s: page does not render %q", flash, want) } if flash == "artifact_sha_invalid" && strings.Contains(body, "Gitea unreachable") { t.Error("artifact_sha_invalid still conflates an unreachable registry with a bad sha") } } }