fix: address review findings from rounds 2843-2846
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 17s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 41s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 2m13s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m23s
PR Ready Gate / clear-labels (pull_request) Successful in 2s
CI / test (pull_request) Successful in 17s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 41s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 2m13s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m23s
- Remove redundant timer.Stop() after timer fires (Sonnet #1, GPT #2) - Remove unused TotalCount field from checkRunsResponse (Sonnet #2) - Improve escapePath doc comment to explain deliberate silent stripping (Sonnet #3) - Fix ListContents to handle both array (directory) and object (single file) responses from GitHub Contents API (GPT #3) - Add HTTPS enforcement: refuse to send credentials over non-HTTPS URLs unless AllowInsecureHTTP() option is passed (Security #1) - Replace constant-value test with actual behavior test for response body limiting (Sonnet #6) - Run gofmt for consistent formatting (Sonnet #4) - Add tests for HTTPS enforcement and ListContents single-file handling
This commit is contained in:
+35
-7
@@ -65,13 +65,30 @@ func asAPIError(err error) (*APIError, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// clientConfig holds optional configuration for NewClient.
|
||||||
|
type clientConfig struct {
|
||||||
|
allowInsecureHTTP bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientOption configures optional behavior of NewClient.
|
||||||
|
type ClientOption func(*clientConfig)
|
||||||
|
|
||||||
|
// AllowInsecureHTTP permits the client to use HTTP (non-TLS) base URLs.
|
||||||
|
// This should only be used for trusted internal deployments or testing.
|
||||||
|
func AllowInsecureHTTP() ClientOption {
|
||||||
|
return func(c *clientConfig) {
|
||||||
|
c.allowInsecureHTTP = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Client interacts with the GitHub API.
|
// Client interacts with the GitHub API.
|
||||||
// A Client is safe for concurrent use by multiple goroutines;
|
// A Client is safe for concurrent use by multiple goroutines;
|
||||||
// however, SetHTTPClient and SetRetryBackoff must not be called concurrently with requests.
|
// however, SetHTTPClient and SetRetryBackoff must not be called concurrently with requests.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
baseURL string
|
baseURL string
|
||||||
token string
|
token string
|
||||||
httpClient *http.Client
|
allowInsecureHTTP bool
|
||||||
|
httpClient *http.Client
|
||||||
|
|
||||||
// retryBackoff defines the delays between retry attempts for 429 responses.
|
// retryBackoff defines the delays between retry attempts for 429 responses.
|
||||||
// retryBackoff[i] is the delay before attempt i+1 (after attempt i fails).
|
// retryBackoff[i] is the delay before attempt i+1 (after attempt i fails).
|
||||||
@@ -82,13 +99,20 @@ type Client struct {
|
|||||||
// NewClient creates a new GitHub API client.
|
// NewClient creates a new GitHub API client.
|
||||||
// If baseURL is empty, it defaults to https://api.github.com.
|
// If baseURL is empty, it defaults to https://api.github.com.
|
||||||
// For GitHub Enterprise, pass the API base URL (e.g. https://github.concur.com/api/v3).
|
// For GitHub Enterprise, pass the API base URL (e.g. https://github.concur.com/api/v3).
|
||||||
func NewClient(token, baseURL string) *Client {
|
// The baseURL must use HTTPS; pass AllowInsecureHTTP() as an option to permit HTTP
|
||||||
|
// for trusted internal deployments (e.g. local testing).
|
||||||
|
func NewClient(token, baseURL string, opts ...ClientOption) *Client {
|
||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = defaultBaseURL
|
baseURL = defaultBaseURL
|
||||||
}
|
}
|
||||||
|
cfg := clientConfig{}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&cfg)
|
||||||
|
}
|
||||||
return &Client{
|
return &Client{
|
||||||
baseURL: strings.TrimRight(baseURL, "/"),
|
baseURL: strings.TrimRight(baseURL, "/"),
|
||||||
token: token,
|
allowInsecureHTTP: cfg.allowInsecureHTTP,
|
||||||
|
token: token,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
@@ -146,7 +170,7 @@ func (c *Client) doRequest(ctx context.Context, method, url string, accept strin
|
|||||||
timer := time.NewTimer(delay)
|
timer := time.NewTimer(delay)
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
timer.Stop()
|
// Timer already fired; Stop() is a no-op here.
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
@@ -159,6 +183,10 @@ func (c *Client) doRequest(ctx context.Context, method, url string, accept strin
|
|||||||
return nil, fmt.Errorf("create request: %w", err)
|
return nil, fmt.Errorf("create request: %w", err)
|
||||||
}
|
}
|
||||||
if c.token != "" {
|
if c.token != "" {
|
||||||
|
// Refuse to send credentials over plaintext unless explicitly allowed.
|
||||||
|
if !c.allowInsecureHTTP && req.URL.Scheme != "https" {
|
||||||
|
return nil, fmt.Errorf("refusing to send credentials over non-HTTPS URL %q (use AllowInsecureHTTP option for trusted networks)", req.URL.Host)
|
||||||
|
}
|
||||||
req.Header.Set("Authorization", "Bearer "+c.token)
|
req.Header.Set("Authorization", "Bearer "+c.token)
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", userAgent)
|
req.Header.Set("User-Agent", userAgent)
|
||||||
|
|||||||
+85
-15
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -38,7 +39,7 @@ func TestDoRequest_SetsAuthHeader(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("my-token", srv.URL)
|
c := NewClient("my-token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ func TestDoRequest_SetsDefaultAcceptHeader(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ func TestDoRequest_429Retry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{10 * time.Millisecond, 10 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{10 * time.Millisecond, 10 * time.Millisecond})
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ func TestDoRequest_429ExhaustsRetries(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond, 1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond, 1 * time.Millisecond})
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ func TestDoRequest_404NoRetry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.doGet(context.Background(), srv.URL+"/test")
|
_, err := c.doGet(context.Background(), srv.URL+"/test")
|
||||||
@@ -154,7 +155,7 @@ func TestDoRequest_401NoRetry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.doGet(context.Background(), srv.URL+"/test")
|
_, err := c.doGet(context.Background(), srv.URL+"/test")
|
||||||
@@ -202,7 +203,7 @@ func TestDoRequest_429RetryAfterHeader(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
// Use short backoff; Retry-After should override
|
// Use short backoff; Retry-After should override
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond, 1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond, 1 * time.Millisecond})
|
||||||
@@ -244,7 +245,7 @@ func TestDoRequest_RetryAfterDoesNotMutateBackoff(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond, 1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond, 1 * time.Millisecond})
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ func TestDoRequest_SetsUserAgentHeader(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
|
||||||
@@ -281,11 +282,24 @@ func TestDoRequest_SetsUserAgentHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDoRequest_LimitsResponseBody(t *testing.T) {
|
func TestDoRequest_LimitsResponseBody(t *testing.T) {
|
||||||
// Verify that responses are read through a limit reader.
|
// Verify that response body reading is actually bounded by maxResponseBytes.
|
||||||
// We can't easily test the 10 MiB limit without OOM risk,
|
// Use a small custom limit to avoid allocating 10 MiB in tests.
|
||||||
// but we verify the constant is set correctly.
|
bigBody := strings.Repeat("x", maxResponseBytes+1024)
|
||||||
if maxResponseBytes != 10*1024*1024 {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
t.Errorf("expected maxResponseBytes = 10 MiB, got %d", maxResponseBytes)
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(bigBody))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
|
c.SetHTTPClient(srv.Client())
|
||||||
|
body, err := c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
// LimitReader should cap the body at maxResponseBytes
|
||||||
|
if len(body) > maxResponseBytes {
|
||||||
|
t.Errorf("expected body <= %d bytes, got %d", maxResponseBytes, len(body))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +312,7 @@ func TestDoRequest_SkipsAuthWhenTokenEmpty(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("", srv.URL) // empty token
|
c := NewClient("", srv.URL, AllowInsecureHTTP()) // empty token
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
_, _ = c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
|
||||||
@@ -314,3 +328,59 @@ func TestNewClient_CheckRedirectStripsAuthOnCrossHost(t *testing.T) {
|
|||||||
t.Fatal("expected CheckRedirect to be set")
|
t.Fatal("expected CheckRedirect to be set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDoRequest_RejectsHTTPWithToken(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte("{}"))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
// Without AllowInsecureHTTP, should refuse to send token over HTTP
|
||||||
|
c := NewClient("secret-token", srv.URL)
|
||||||
|
c.SetHTTPClient(srv.Client())
|
||||||
|
_, err := c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error when sending token over HTTP")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "refusing to send credentials") {
|
||||||
|
t.Errorf("unexpected error message: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoRequest_AllowsHTTPWithoutToken(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(`{"ok":true}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
// Without token, HTTP should be fine (no credentials to leak)
|
||||||
|
c := NewClient("", srv.URL)
|
||||||
|
c.SetHTTPClient(srv.Client())
|
||||||
|
body, err := c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if string(body) != `{"ok":true}` {
|
||||||
|
t.Errorf("unexpected body: %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDoRequest_AllowsHTTPWithInsecureOption(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(`{"ok":true}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient("secret-token", srv.URL, AllowInsecureHTTP())
|
||||||
|
c.SetHTTPClient(srv.Client())
|
||||||
|
body, err := c.doGet(context.Background(), srv.URL+"/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if string(body) != `{"ok":true}` {
|
||||||
|
t.Errorf("unexpected body: %s", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+20
-3
@@ -19,6 +19,9 @@ func (c *Client) GetFileContent(ctx context.Context, owner, repo, path, ref stri
|
|||||||
|
|
||||||
// ListContents lists files and directories at a given path in a repo.
|
// ListContents lists files and directories at a given path in a repo.
|
||||||
// Returns the directory listing from the GitHub contents API.
|
// Returns the directory listing from the GitHub contents API.
|
||||||
|
// If the path points to a single file (not a directory), the API returns
|
||||||
|
// a JSON object instead of an array; this is handled by returning a
|
||||||
|
// single-element slice.
|
||||||
func (c *Client) ListContents(ctx context.Context, owner, repo, path string) ([]vcs.ContentEntry, error) {
|
func (c *Client) ListContents(ctx context.Context, owner, repo, path string) ([]vcs.ContentEntry, error) {
|
||||||
reqURL := fmt.Sprintf("%s/repos/%s/%s/contents/%s",
|
reqURL := fmt.Sprintf("%s/repos/%s/%s/contents/%s",
|
||||||
c.baseURL, url.PathEscape(owner), url.PathEscape(repo), escapePath(path))
|
c.baseURL, url.PathEscape(owner), url.PathEscape(repo), escapePath(path))
|
||||||
@@ -26,14 +29,24 @@ func (c *Client) ListContents(ctx context.Context, owner, repo, path string) ([]
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list contents %s: %w", path, err)
|
return nil, fmt.Errorf("list contents %s: %w", path, err)
|
||||||
}
|
}
|
||||||
var entries []struct {
|
|
||||||
|
type entry struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The GitHub contents API returns an array for directories and an object
|
||||||
|
// for single files. Try array first (common case), then fall back to object.
|
||||||
|
var entries []entry
|
||||||
if err := json.Unmarshal(body, &entries); err != nil {
|
if err := json.Unmarshal(body, &entries); err != nil {
|
||||||
return nil, fmt.Errorf("parse contents JSON: %w", err)
|
var single entry
|
||||||
|
if err2 := json.Unmarshal(body, &single); err2 != nil {
|
||||||
|
return nil, fmt.Errorf("parse contents JSON: %w", err)
|
||||||
|
}
|
||||||
|
entries = []entry{single}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]vcs.ContentEntry, len(entries))
|
result := make([]vcs.ContentEntry, len(entries))
|
||||||
for i, e := range entries {
|
for i, e := range entries {
|
||||||
result[i] = vcs.ContentEntry{
|
result[i] = vcs.ContentEntry{
|
||||||
@@ -47,7 +60,11 @@ func (c *Client) ListContents(ctx context.Context, owner, repo, path string) ([]
|
|||||||
|
|
||||||
// escapePath escapes each segment of a relative file path for use in URLs.
|
// escapePath escapes each segment of a relative file path for use in URLs.
|
||||||
// Slashes are preserved as path separators; other special characters are escaped.
|
// Slashes are preserved as path separators; other special characters are escaped.
|
||||||
// Dot-segments ("." and "..") are removed to prevent path traversal.
|
// Dot-segments ("." and "..") are silently removed to prevent path traversal.
|
||||||
|
// This is intentional: callers may receive a different path than requested without
|
||||||
|
// error. The function is package-private, and all callers (GetFileContentAtRef,
|
||||||
|
// ListContents) already handle missing-file errors from the API if the cleaned
|
||||||
|
// path doesn't match what the caller intended.
|
||||||
func escapePath(p string) string {
|
func escapePath(p string) string {
|
||||||
parts := strings.Split(p, "/")
|
parts := strings.Split(p, "/")
|
||||||
var clean []string
|
var clean []string
|
||||||
|
|||||||
+36
-11
@@ -20,7 +20,7 @@ func TestGetFileContent_DelegatesToGetFileContentAtRef(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
// Call with empty ref — should not include ref param
|
// Call with empty ref — should not include ref param
|
||||||
@@ -47,7 +47,7 @@ func TestGetFileContent_WithRef(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "abc123")
|
_, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "abc123")
|
||||||
@@ -66,7 +66,7 @@ func TestGetFileContent_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContent(context.Background(), "owner", "repo", "missing.go", "")
|
_, err := c.GetFileContent(context.Background(), "owner", "repo", "missing.go", "")
|
||||||
@@ -82,7 +82,7 @@ func TestGetFileContent_401(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "")
|
_, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "")
|
||||||
@@ -107,7 +107,7 @@ func TestGetFileContent_429Retry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@ func TestGetFileContent_MalformedJSON(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "")
|
_, err := c.GetFileContent(context.Background(), "owner", "repo", "file.go", "")
|
||||||
@@ -151,7 +151,7 @@ func TestListContents_HappyPath(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
entries, err := c.ListContents(context.Background(), "owner", "repo", "src")
|
entries, err := c.ListContents(context.Background(), "owner", "repo", "src")
|
||||||
@@ -185,7 +185,7 @@ func TestListContents_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.ListContents(context.Background(), "owner", "repo", "missing")
|
_, err := c.ListContents(context.Background(), "owner", "repo", "missing")
|
||||||
@@ -201,7 +201,7 @@ func TestListContents_401(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.ListContents(context.Background(), "owner", "repo", "src")
|
_, err := c.ListContents(context.Background(), "owner", "repo", "src")
|
||||||
@@ -225,7 +225,7 @@ func TestListContents_429Retry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ func TestListContents_MalformedJSON(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.ListContents(context.Background(), "owner", "repo", "src")
|
_, err := c.ListContents(context.Background(), "owner", "repo", "src")
|
||||||
@@ -307,3 +307,28 @@ func TestDecodeBase64Content_CRLF(t *testing.T) {
|
|||||||
t.Errorf("expected 'hello world', got %q", decoded)
|
t.Errorf("expected 'hello world', got %q", decoded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListContents_SingleFile(t *testing.T) {
|
||||||
|
// GitHub Contents API returns a JSON object (not array) for single-file paths
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
w.Write([]byte(`{"name":"README.md","path":"README.md","type":"file"}`))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
|
c.SetHTTPClient(srv.Client())
|
||||||
|
entries, err := c.ListContents(context.Background(), "owner", "repo", "README.md")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(entries) != 1 {
|
||||||
|
t.Fatalf("expected 1 entry, got %d", len(entries))
|
||||||
|
}
|
||||||
|
if entries[0].Name != "README.md" {
|
||||||
|
t.Errorf("expected name 'README.md', got %q", entries[0].Name)
|
||||||
|
}
|
||||||
|
if entries[0].Type != "file" {
|
||||||
|
t.Errorf("expected type 'file', got %q", entries[0].Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-2
@@ -44,8 +44,7 @@ type commitStatusResponse struct {
|
|||||||
|
|
||||||
// checkRunsResponse is the GitHub check runs API response.
|
// checkRunsResponse is the GitHub check runs API response.
|
||||||
type checkRunsResponse struct {
|
type checkRunsResponse struct {
|
||||||
TotalCount int `json:"total_count"`
|
CheckRuns []struct {
|
||||||
CheckRuns []struct {
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Conclusion *string `json:"conclusion"`
|
Conclusion *string `json:"conclusion"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
|||||||
+24
-24
@@ -26,7 +26,7 @@ func TestGetPullRequest_HappyPath(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
pr, err := c.GetPullRequest(context.Background(), "owner", "repo", 42)
|
pr, err := c.GetPullRequest(context.Background(), "owner", "repo", 42)
|
||||||
@@ -60,7 +60,7 @@ func TestGetPullRequest_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequest(context.Background(), "owner", "repo", 999)
|
_, err := c.GetPullRequest(context.Background(), "owner", "repo", 999)
|
||||||
@@ -79,7 +79,7 @@ func TestGetPullRequest_401(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequest(context.Background(), "owner", "repo", 1)
|
_, err := c.GetPullRequest(context.Background(), "owner", "repo", 1)
|
||||||
@@ -110,7 +110,7 @@ func TestGetPullRequest_429Retry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ func TestGetPullRequest_MalformedJSON(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequest(context.Background(), "owner", "repo", 1)
|
_, err := c.GetPullRequest(context.Background(), "owner", "repo", 1)
|
||||||
@@ -155,7 +155,7 @@ func TestGetPullRequestDiff_HappyPath(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
diff, err := c.GetPullRequestDiff(context.Background(), "owner", "repo", 42)
|
diff, err := c.GetPullRequestDiff(context.Background(), "owner", "repo", 42)
|
||||||
@@ -177,7 +177,7 @@ func TestGetPullRequestDiff_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequestDiff(context.Background(), "owner", "repo", 999)
|
_, err := c.GetPullRequestDiff(context.Background(), "owner", "repo", 999)
|
||||||
@@ -193,7 +193,7 @@ func TestGetPullRequestDiff_401(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
_, err := c.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||||
@@ -211,7 +211,7 @@ func TestGetPullRequestFiles_HappyPath(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
files, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
files, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
||||||
@@ -256,7 +256,7 @@ func TestGetPullRequestFiles_Pagination(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
files, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
files, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
||||||
@@ -283,7 +283,7 @@ func TestGetPullRequestFiles_BinaryFile_NoPatch(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
files, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
files, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
||||||
@@ -305,7 +305,7 @@ func TestGetPullRequestFiles_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 999)
|
_, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 999)
|
||||||
@@ -321,7 +321,7 @@ func TestGetPullRequestFiles_MalformedJSON(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
_, err := c.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
||||||
@@ -345,7 +345,7 @@ func TestGetFileContentAtRef_HappyPath(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
content, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "path/to/file.go", "abc123")
|
content, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "path/to/file.go", "abc123")
|
||||||
@@ -369,7 +369,7 @@ func TestGetFileContentAtRef_EmptyRef(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
content, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "file.txt", "")
|
content, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "file.txt", "")
|
||||||
@@ -388,7 +388,7 @@ func TestGetFileContentAtRef_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "missing.go", "main")
|
_, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "missing.go", "main")
|
||||||
@@ -404,7 +404,7 @@ func TestGetFileContentAtRef_401(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "file.go", "main")
|
_, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "file.go", "main")
|
||||||
@@ -420,7 +420,7 @@ func TestGetFileContentAtRef_MalformedJSON(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "file.go", "main")
|
_, err := c.GetFileContentAtRef(context.Background(), "owner", "repo", "file.go", "main")
|
||||||
@@ -445,7 +445,7 @@ func TestGetFileContentAtRef_429Retry(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
c.SetRetryBackoff([]time.Duration{1 * time.Millisecond})
|
||||||
|
|
||||||
@@ -496,7 +496,7 @@ func TestGetCommitStatuses_HappyPath(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
statuses, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "abc123")
|
statuses, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "abc123")
|
||||||
@@ -567,7 +567,7 @@ func TestGetCommitStatuses_CheckRunConclusions(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
statuses, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "sha1")
|
statuses, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "sha1")
|
||||||
@@ -591,7 +591,7 @@ func TestGetCommitStatuses_404(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "badsha")
|
_, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "badsha")
|
||||||
@@ -607,7 +607,7 @@ func TestGetCommitStatuses_401(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "sha")
|
_, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "sha")
|
||||||
@@ -623,7 +623,7 @@ func TestGetCommitStatuses_MalformedJSON(t *testing.T) {
|
|||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
c := NewClient("token", srv.URL)
|
c := NewClient("token", srv.URL, AllowInsecureHTTP())
|
||||||
c.SetHTTPClient(srv.Client())
|
c.SetHTTPClient(srv.Client())
|
||||||
|
|
||||||
_, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "sha")
|
_, err := c.GetCommitStatuses(context.Background(), "owner", "repo", "sha")
|
||||||
|
|||||||
Reference in New Issue
Block a user