package gitea import ( "context" "errors" "net/http" "net/http/httptest" "strings" "testing" ) // ListVersions filters to the requested package name and returns versions newest-semver first. func TestListVersions_FilterAndSort(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !strings.HasPrefix(r.URL.Path, "/api/v1/packages/admin") { http.NotFound(w, r) return } // Intentionally unsorted + a foreign package that must be filtered out. w.Header().Set("Content-Type", "application/json") w.Write([]byte(`[ {"name":"felhom-agent","version":"0.43.0","type":"generic"}, {"name":"felhom-agent","version":"0.52.0","type":"generic"}, {"name":"felhom-golden","version":"0.85.1","type":"generic"}, {"name":"felhom-agent","version":"0.9.0","type":"generic"} ]`)) })) defer srv.Close() c := New(srv.URL, "admin", "u", "t") vers, err := c.ListVersions(context.Background(), "felhom-agent") if err != nil { t.Fatalf("ListVersions: %v", err) } // Only felhom-agent, newest semver first (0.52.0 > 0.43.0 > 0.9.0 — numeric, not lexical). want := []string{"0.52.0", "0.43.0", "0.9.0"} if len(vers) != len(want) { t.Fatalf("got %v, want %v", vers, want) } for i := range want { if vers[i] != want[i] { t.Fatalf("got %v, want %v", vers, want) } } } // FileSHA256 prefers an exact filename match; otherwise it falls back to the first file. func TestFileSHA256_PreferredAndFallback(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch { case strings.Contains(r.URL.Path, "/felhom-golden/0.85.1/files"): w.Write([]byte(`[ {"name":"stray.txt","sha256":"aaaa"}, {"name":"golden.tar.zst","sha256":"f87031cc"} ]`)) case strings.Contains(r.URL.Path, "/felhom-agent/0.52.0/files"): w.Write([]byte(`[{"name":"felhom-agent","sha256":"5bfc690c"}]`)) default: http.NotFound(w, r) } })) defer srv.Close() c := New(srv.URL, "admin", "u", "t") // preferred-file match among multiple files sha, err := c.FileSHA256(context.Background(), "felhom-golden", "0.85.1", "golden.tar.zst") if err != nil || sha != "f87031cc" { t.Fatalf("golden sha: got %q err %v, want f87031cc", sha, err) } // single-file version resolves regardless of preferred name (fallback to first) sha, err = c.FileSHA256(context.Background(), "felhom-agent", "0.52.0", "felhom-agent") if err != nil || sha != "5bfc690c" { t.Fatalf("agent sha: got %q err %v, want 5bfc690c", sha, err) } } // A non-200 from Gitea surfaces as an error (so a save refuses rather than storing a blank sha). func TestGetJSON_ErrorOnNon200(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "nope", http.StatusInternalServerError) })) defer srv.Close() c := New(srv.URL, "admin", "u", "t") if _, err := c.ListVersions(context.Background(), "felhom-agent"); err == nil { t.Fatal("expected an error on HTTP 500") } } // R-1011: with a file named, a version without it is ErrNoFile (which is also ErrNoSHA) — never the first file's sha. // Seen 2026-10-14: the Day-0 list showed agent `0.156.1-step1` (only felhom-config-bundle.json) with the bundle's sha. // // COMPANION RED-PROOF (observed 2026-10-19): with the pre-fix fallback to files[0], it fails with // // gitea_test.go: step bundle: sha "bbbb" err , want ErrNoFile and no sha // // Restored. func TestFileSHA256_R1011_NeverAnotherFilesSHA(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch { case strings.Contains(r.URL.Path, "/felhom-agent/0.156.1-step1/files"): w.Write([]byte(`[{"name":"felhom-config-bundle.json","sha256":"bbbb"}]`)) case strings.Contains(r.URL.Path, "/felhom-agent/0.156.1/files"): w.Write([]byte(`[{"name":"felhom-config-bundle.json","sha256":"bbbb"},{"name":"felhom-agent","sha256":"aaaa"}]`)) case strings.Contains(r.URL.Path, "/felhom-agent/0.1.0/files"): w.Write([]byte(`[{"name":"felhom-agent","sha256":""}]`)) default: http.NotFound(w, r) } })) defer srv.Close() c := New(srv.URL, "admin", "u", "t") ctx := context.Background() sha, err := c.FileSHA256(ctx, "felhom-agent", "0.156.1-step1", "felhom-agent") if sha != "" || !errors.Is(err, ErrNoFile) || !errors.Is(err, ErrNoSHA) { t.Fatalf("step bundle: sha %q err %v, want ErrNoFile and no sha", sha, err) } if sha, err = c.FileSHA256(ctx, "felhom-agent", "0.156.1", "felhom-agent"); err != nil || sha != "aaaa" { t.Fatalf("the release beside its bundle: sha %q err %v, want the binary's aaaa", sha, err) } if sha, err = c.FileSHA256(ctx, "felhom-agent", "0.1.0", "felhom-agent"); sha != "" || !errors.Is(err, ErrNoSHA) || errors.Is(err, ErrNoFile) { t.Fatalf("the file without a sha: sha %q err %v, want ErrNoSHA (not ErrNoFile)", sha, err) } if sha, err = c.FileSHA256(ctx, "felhom-agent", "0.156.1-step1", ""); err != nil || sha != "bbbb" { t.Fatalf("no file named: the first file stays the answer, got %q %v", sha, err) } }