//go:build e2e package e2e import ( "encoding/json" "io" "net/http" "net/http/httptest" "strconv" "testing" "time" "git.unkin.net/unkin/artifactapi/internal/testsupport" ) // assertHeadMatchesGet checks HEAD returns GET's status, type and length with no body. func assertHeadMatchesGet(t *testing.T, url string) { t.Helper() get, err := http.Get(url) if err != nil { t.Fatalf("GET: %v", err) } body, _ := io.ReadAll(get.Body) get.Body.Close() if get.StatusCode != http.StatusOK { t.Fatalf("GET %s: %d %s", url, get.StatusCode, body) } head, err := http.Head(url) if err != nil { t.Fatalf("HEAD: %v", err) } hb, _ := io.ReadAll(head.Body) head.Body.Close() if head.StatusCode != http.StatusOK { t.Fatalf("HEAD %s: got %d, want 200", url, head.StatusCode) } if len(hb) != 0 { t.Errorf("HEAD returned a %d-byte body", len(hb)) } if head.Header.Get("Content-Type") != get.Header.Get("Content-Type") { t.Errorf("HEAD Content-Type %q, GET %q", head.Header.Get("Content-Type"), get.Header.Get("Content-Type")) } if head.ContentLength != int64(len(body)) { t.Errorf("HEAD Content-Length %d, GET body %d bytes", head.ContentLength, len(body)) } } func TestProxyHeadNuGetRewritten(t *testing.T) { var upstream *httptest.Server upstream = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{"version": "3.0.0", "resources": []map[string]string{ {"@id": upstream.URL + "/v3-flatcontainer/", "@type": "PackageBaseAddress/3.0.0"}, }}) })) defer upstream.Close() createRemote(t, mustFormat(`{"name": "head-nuget", "package_type": "nuget", "base_url": %q}`, upstream.URL+"/v3/index.json")) defer deleteRemote(t, "head-nuget") assertHeadMatchesGet(t, apiURL("/api/v1/remote/head-nuget/index.json")) } func TestProxyHeadGitHubRPMRepodata(t *testing.T) { rpm := testsupport.MinimalRPM("demo", "1.2", "3", "x86_64") var gh *httptest.Server mux := http.NewServeMux() mux.HandleFunc("/repos/acme/tools/releases", func(w http.ResponseWriter, r *http.Request) { if p := r.URL.Query().Get("page"); p != "" && p != "1" { w.Write([]byte("[]")) return } json.NewEncoder(w).Encode([]map[string]any{{"tag_name": "v1.2-3", "assets": []map[string]any{{ "name": "demo-1.2-3.x86_64.rpm", "size": len(rpm), "browser_download_url": gh.URL + "/acme/tools/releases/download/v1.2-3/demo-1.2-3.x86_64.rpm", }}}}) }) mux.HandleFunc("/acme/tools/releases/download/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", strconv.Itoa(len(rpm))) w.Write(rpm) }) gh = httptest.NewServer(mux) defer gh.Close() createRemote(t, mustFormat(`{"name": "head-ghrpm", "package_type": "github_rpm", "base_url": %q, "releases_remote": "github"}`, gh.URL+"/repos/acme/tools")) defer deleteRemote(t, "head-ghrpm") url := apiURL("/api/v1/remote/head-ghrpm/repodata/repomd.xml") deadline := time.Now().Add(30 * time.Second) for { if _, status := getBody(t, url); status == http.StatusOK || time.Now().After(deadline) { break } time.Sleep(500 * time.Millisecond) } assertHeadMatchesGet(t, url) }