package github import ( "context" "net/http" "net/http/httptest" "testing" "time" ) func TestNewClient_DefaultBaseURL(t *testing.T) { c := NewClient("test-token", "") if c.baseURL != "https://api.github.com" { t.Errorf("expected default base URL, got %q", c.baseURL) } } func TestNewClient_CustomBaseURL(t *testing.T) { c := NewClient("test-token", "https://github.concur.com/api/v3") if c.baseURL != "https://github.concur.com/api/v3" { t.Errorf("expected custom base URL, got %q", c.baseURL) } } func TestNewClient_TrimsTrailingSlash(t *testing.T) { c := NewClient("test-token", "https://github.concur.com/api/v3/") if c.baseURL != "https://github.concur.com/api/v3" { t.Errorf("expected trailing slash trimmed, got %q", c.baseURL) } } func TestDoRequest_SetsAuthHeader(t *testing.T) { var gotAuth string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuth = r.Header.Get("Authorization") w.WriteHeader(200) w.Write([]byte("{}")) })) defer srv.Close() c := NewClient("my-token", srv.URL) c.SetHTTPClient(srv.Client()) _, _ = c.doGet(context.Background(), srv.URL+"/test") if gotAuth != "Bearer my-token" { t.Errorf("expected Bearer auth, got %q", gotAuth) } } func TestDoRequest_SetsDefaultAcceptHeader(t *testing.T) { var gotAccept string srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAccept = r.Header.Get("Accept") w.WriteHeader(200) w.Write([]byte("{}")) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, _ = c.doGet(context.Background(), srv.URL+"/test") if gotAccept != "application/vnd.github+json" { t.Errorf("expected default Accept header, got %q", gotAccept) } } func TestDoRequest_429Retry(t *testing.T) { attempts := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ if attempts == 1 { w.Header().Set("Retry-After", "1") w.WriteHeader(429) w.Write([]byte(`{"message":"rate limit"}`)) return } w.WriteHeader(200) w.Write([]byte(`{"ok":true}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) c.RetryBackoff = []time.Duration{10 * time.Millisecond, 10 * time.Millisecond} 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) } if attempts != 2 { t.Errorf("expected 2 attempts, got %d", attempts) } } func TestDoRequest_429ExhaustsRetries(t *testing.T) { attempts := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ w.WriteHeader(429) w.Write([]byte(`{"message":"rate limit"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) c.RetryBackoff = []time.Duration{1 * time.Millisecond, 1 * time.Millisecond} _, err := c.doGet(context.Background(), srv.URL+"/test") if err == nil { t.Fatal("expected error after exhausting retries") } apiErr, ok := err.(*APIError) if !ok { t.Fatalf("expected *APIError, got %T", err) } if apiErr.StatusCode != 429 { t.Errorf("expected 429, got %d", apiErr.StatusCode) } if attempts != 3 { t.Errorf("expected 3 attempts, got %d", attempts) } } func TestDoRequest_404NoRetry(t *testing.T) { attempts := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ w.WriteHeader(404) w.Write([]byte(`{"message":"not found"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.doGet(context.Background(), srv.URL+"/test") if err == nil { t.Fatal("expected error for 404") } if attempts != 1 { t.Errorf("expected 1 attempt (no retry on 404), got %d", attempts) } } func TestDoRequest_401NoRetry(t *testing.T) { attempts := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts++ w.WriteHeader(401) w.Write([]byte(`{"message":"bad credentials"}`)) })) defer srv.Close() c := NewClient("token", srv.URL) c.SetHTTPClient(srv.Client()) _, err := c.doGet(context.Background(), srv.URL+"/test") if err == nil { t.Fatal("expected error for 401") } if attempts != 1 { t.Errorf("expected 1 attempt (no retry on 401), got %d", attempts) } } func TestIsNotFound(t *testing.T) { err := &APIError{StatusCode: 404, Body: "not found"} if !IsNotFound(err) { t.Error("expected IsNotFound to return true for 404") } err2 := &APIError{StatusCode: 500, Body: "server error"} if IsNotFound(err2) { t.Error("expected IsNotFound to return false for 500") } } func TestIsUnauthorized(t *testing.T) { err := &APIError{StatusCode: 401, Body: "bad credentials"} if !IsUnauthorized(err) { t.Error("expected IsUnauthorized to return true for 401") } }