Files

132 lines
5.1 KiB
Go

package forge
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
)
func TestNextLink(t *testing.T) {
cases := map[string]string{
``: ``,
`<https://api.github.com/repositories/1/issues?page=2>; rel="next", <https://api.github.com/repositories/1/issues?page=9>; rel="last"`: `https://api.github.com/repositories/1/issues?page=2`,
`<https://x/a?page=1>; rel="prev", <https://x/a?page=3>; rel="next"`: `https://x/a?page=3`,
`<https://x/a?page=1>; rel="first", <https://x/a?page=1>; rel="prev"`: ``,
`<https://x/a?labels=a,b&page=2>; rel=next`: `https://x/a?labels=a,b&page=2`,
`<http://localhost/api/v1/repos/search?limit=1&page=2>; rel="next",<http://localhost/api/v1/repos/search?limit=1&page=5252>; rel="last"`: `http://localhost/api/v1/repos/search?limit=1&page=2`,
}
for header, want := range cases {
if got := nextLink(header); got != want {
t.Errorf("nextLink(%q) = %q, want %q", header, got, want)
}
}
}
// pagedServer serves `total` comments `size` at a time at any path, linking each page to the next one under `nextPath`.
func pagedServer(t *testing.T, total, size int, nextPath string, check func(r *http.Request)) (*httptest.Server, *int32) {
t.Helper()
var calls int32
var srv *httptest.Server
srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&calls, 1)
check(r)
page := 1
fmt.Sscan(r.URL.Query().Get("page"), &page)
start := (page - 1) * size
end := min(start+size, total)
if end < total {
w.Header().Set("Link", fmt.Sprintf(`<%s%s?page=%d>; rel="next"`, srv.URL, nextPath, page+1))
}
out := []map[string]any{}
for i := start; i < end; i++ {
out = append(out, map[string]any{"id": i + 1, "body": fmt.Sprintf("c%d", i+1), "user": map[string]any{"login": "bob"}})
}
_ = json.NewEncoder(w).Encode(out)
}))
t.Cleanup(srv.Close)
return srv, &calls
}
func TestGitHubPaginationFollowsLinkToOtherPath(t *testing.T) {
srv, calls := pagedServer(t, 230, 100, "/repositories/42/issues/7/comments", func(r *http.Request) {
if r.Header.Get("Authorization") != "Bearer tok" ||
r.Header.Get("Accept") != "application/vnd.github+json" ||
r.Header.Get("X-GitHub-Api-Version") != "2022-11-28" {
t.Errorf("GitHub 请求头不对: %v", r.Header)
}
if r.URL.Query().Get("page") == "" {
if r.URL.Path != "/repos/o/r/issues/7/comments" || r.URL.Query().Get("per_page") != "100" {
t.Errorf("第一页请求不对: %s", r.URL.RequestURI())
}
} else if r.URL.Path != "/repositories/42/issues/7/comments" {
t.Errorf("后续页应照 Link 走: %s", r.URL.RequestURI())
}
})
list, err := NewGitHub(srv.URL, "tok", "o", "r").ListIssueComments(7)
if err != nil {
t.Fatalf("ListIssueComments 出错: %v", err)
}
if len(list) != 230 || list[229].Body != "c230" || *calls != 3 {
t.Fatalf("应 3 次请求取全 230 条,得到 %d 条 / %d 次", len(list), *calls)
}
}
func TestGiteaPaginationFollowsLinkWithLimit(t *testing.T) {
srv, calls := pagedServer(t, 120, 50, "/api/v1/repos/o/r/issues/7/comments", func(r *http.Request) {
if r.Header.Get("Authorization") != "token tok" {
t.Errorf("Gitea 认证头不对: %q", r.Header.Get("Authorization"))
}
if !strings.HasPrefix(r.URL.Path, "/api/v1/") {
t.Errorf("Gitea 请求应在 /api/v1 下: %s", r.URL.Path)
}
if r.URL.Query().Get("page") == "" {
if r.URL.Query().Get("limit") != "50" {
t.Errorf("第一页应带 limit=50: %s", r.URL.RequestURI())
}
}
})
list, err := NewGitea(srv.URL, "tok", "o", "r").ListIssueComments(7)
if err != nil {
t.Fatalf("ListIssueComments 出错: %v", err)
}
if len(list) != 120 || *calls != 3 {
t.Fatalf("应 3 次请求取全 120 条,得到 %d 条 / %d 次", len(list), *calls)
}
}
func TestPaginationStopsWithoutLinkHeader(t *testing.T) {
srv, calls := pagedServer(t, 120, 1000, "", func(*http.Request) {})
list, err := NewGitea(srv.URL, "tok", "o", "r").ListIssueComments(7)
if err != nil {
t.Fatalf("ListIssueComments 出错: %v", err)
}
if len(list) != 120 || *calls != 1 {
t.Fatalf("没有 Link 头时应一次取完,得到 %d 条 / %d 次", len(list), *calls)
}
}
func TestPaginationRefusesLinkToAnotherHost(t *testing.T) {
var foreignCalls int32
foreign := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&foreignCalls, 1)
_, _ = w.Write([]byte("[]"))
}))
t.Cleanup(foreign.Close)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Link", fmt.Sprintf(`<%s/steal?page=2>; rel="next"`, foreign.URL))
_, _ = w.Write([]byte(`[{"id":1,"body":"x","user":{"login":"bob"}}]`))
}))
t.Cleanup(srv.Close)
_, err := NewGitHub(srv.URL, "tok", "o", "r").ListIssueComments(7)
if err == nil {
t.Fatal("Link 指向别的主机时应报错")
}
if foreignCalls != 0 {
t.Fatalf("不应带 token 请求别的主机,实际请求了 %d 次", foreignCalls)
}
}