Client GitHub avec cache, quota et tests ; README
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1885488d05
commit
9432a34e30
3 files changed
+416
-2
No files matched your search
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// fakeGitHub answers every request with a fixed status, headers and body.
|
||||
type fakeGitHub struct {
|
||||
status int
|
||||
header http.Header
|
||||
body string
|
||||
calls int
|
||||
last *http.Request
|
||||
}
|
||||
|
||||
func (f *fakeGitHub) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
f.calls++
|
||||
f.last = r
|
||||
return &http.Response{
|
||||
StatusCode: f.status,
|
||||
Status: http.StatusText(f.status),
|
||||
Header: f.header,
|
||||
Body: io.NopCloser(strings.NewReader(f.body)),
|
||||
Request: r,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newFake(status int, body string) (*GitHub, *fakeGitHub) {
|
||||
f := &fakeGitHub{status: status, body: body, header: http.Header{
|
||||
"X-Ratelimit-Limit": {"10"},
|
||||
"X-Ratelimit-Remaining": {"9"},
|
||||
"X-Ratelimit-Reset": {"1790851447"},
|
||||
"X-Ratelimit-Resource": {"search"},
|
||||
}}
|
||||
gh := NewGitHub("https://api.github.test", "secret", time.Minute)
|
||||
gh.client = &http.Client{Transport: f}
|
||||
return gh, f
|
||||
}
|
||||
|
||||
const searchBody = `{"total_count": 14, "incomplete_results": false, "items": [{
|
||||
"full_name": "mcuadros/go-syslog", "name": "go-syslog", "html_url": "https://github.com/mcuadros/go-syslog",
|
||||
"description": "Syslog server library for go", "language": "Go", "topics": null,
|
||||
"stargazers_count": 540, "forks_count": 150, "open_issues_count": 20, "archived": false, "fork": false,
|
||||
"created_at": "2014-09-01T10:00:00Z", "pushed_at": "2024-03-01T10:00:00Z",
|
||||
"owner": {"login": "mcuadros", "avatar_url": "https://avatars.githubusercontent.com/u/1573114?v=4"},
|
||||
"license": {"spdx_id": "MIT"}}]}`
|
||||
|
||||
func TestSearchMapsAndCaches(t *testing.T) {
|
||||
gh, f := newFake(http.StatusOK, searchBody)
|
||||
c := Criteria{Text: "syslog", Language: "Go", Sort: "stars", Order: "desc", Page: 1, PerPage: 30}
|
||||
|
||||
res, err := gh.Search(context.Background(), c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.Total != 14 || len(res.Items) != 1 {
|
||||
t.Fatalf("got total %d, %d items", res.Total, len(res.Items))
|
||||
}
|
||||
r := res.Items[0]
|
||||
if r.FullName != "mcuadros/go-syslog" || r.Owner != "mcuadros" || r.License != "MIT" || r.Stars != 540 || r.Topics == nil {
|
||||
t.Errorf("bad mapping: %+v", r)
|
||||
}
|
||||
if res.Rate.Limit != 10 || res.Rate.Remaining != 9 {
|
||||
t.Errorf("bad rate: %+v", res.Rate)
|
||||
}
|
||||
q := f.last.URL.Query()
|
||||
if q.Get("sort") != "stars" || q.Get("q") != "syslog language:Go archived:false" {
|
||||
t.Errorf("bad request: %s", f.last.URL)
|
||||
}
|
||||
if f.last.Header.Get("Authorization") != "Bearer secret" {
|
||||
t.Errorf("token not sent")
|
||||
}
|
||||
|
||||
res, err = gh.Search(context.Background(), c)
|
||||
if err != nil || !res.Cached || f.calls != 1 {
|
||||
t.Errorf("second identical search should come from the cache (calls=%d, cached=%v, err=%v)", f.calls, res.Cached, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchErrors(t *testing.T) {
|
||||
for status, code := range map[int]string{
|
||||
http.StatusForbidden: "rate_limited",
|
||||
http.StatusUnauthorized: "github_token",
|
||||
http.StatusUnprocessableEntity: "bad_query",
|
||||
} {
|
||||
gh, _ := newFake(status, `{"message": "nope", "errors": [{"message": "detail"}]}`)
|
||||
_, err := gh.Search(context.Background(), Criteria{Page: 1, PerPage: 30})
|
||||
ce, ok := err.(*codedError)
|
||||
if !ok || ce.code != code || ce.detail != "detail" {
|
||||
t.Errorf("status %d: got %v, want code %s", status, err, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user