f07220b0c6
- Add internal/node package: reads/writes package.json version (single or multi-path via node.package_json / node.package_jsons) - Add maven.pom_paths support: update multiple pom.xml files in one release commit; pom_paths overrides pom_path; --pom flag clears pom_paths - Add git.bump_rules config: per-type control of which version component bumps (breaking/feat/fix accept "patch" or "minor"); wired through version.Next() as a new sixth parameter - Extract injectable function vars (absPath, gitAllCommits, gitCommitsSince, gitCommitFiles) to enable error-path testing without interfaces - Achieve 100% per-package statement coverage across all 12 packages Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
139 lines
4.1 KiB
Go
139 lines
4.1 KiB
Go
package ghclient
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNew(t *testing.T) {
|
|
c := New("tok", "owner/repo")
|
|
if c.token != "tok" || c.repo != "owner/repo" || c.httpClient == nil {
|
|
t.Fatalf("New fields: token=%q repo=%q httpClient=%v", c.token, c.repo, c.httpClient)
|
|
}
|
|
}
|
|
|
|
func TestCreateReleaseSuccess(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("method = %q, want POST", r.Method)
|
|
}
|
|
if !strings.HasSuffix(r.URL.Path, "/releases") {
|
|
t.Errorf("path = %q, want .../releases", r.URL.Path)
|
|
}
|
|
if r.Header.Get("Authorization") != "Bearer test-token" {
|
|
t.Errorf("Authorization = %q", r.Header.Get("Authorization"))
|
|
}
|
|
var req struct {
|
|
TagName string `json:"tag_name"`
|
|
Name string `json:"name"`
|
|
Body string `json:"body"`
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
if req.TagName != "v1.0.0" {
|
|
t.Errorf("tag_name = %q, want v1.0.0", req.TagName)
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
io.WriteString(w, `{}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := New("test-token", "owner/repo")
|
|
c.httpClient = srv.Client()
|
|
// Override the URL by pointing the client at the test server.
|
|
// We can't easily override the URL without a custom transport, so use a
|
|
// round-trip wrapper instead.
|
|
c.httpClient.Transport = rewriteTransport{base: http.DefaultTransport, target: srv.URL}
|
|
|
|
if err := c.CreateRelease(context.Background(), "v1.0.0", "release notes"); err != nil {
|
|
t.Fatalf("CreateRelease: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateReleaseAPIError(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
io.WriteString(w, `{"message":"Validation Failed"}`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := New("tok", "owner/repo")
|
|
c.httpClient = &http.Client{Transport: rewriteTransport{base: http.DefaultTransport, target: srv.URL}}
|
|
|
|
err := c.CreateRelease(context.Background(), "v1.0.0", "body")
|
|
if err == nil {
|
|
t.Fatal("expected error for 422 response")
|
|
}
|
|
if !strings.Contains(err.Error(), "422") {
|
|
t.Errorf("error should mention status code: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateReleaseAPIErrorNoMessage(t *testing.T) {
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
io.WriteString(w, `not json`)
|
|
}))
|
|
defer srv.Close()
|
|
|
|
c := New("tok", "owner/repo")
|
|
c.httpClient = &http.Client{Transport: rewriteTransport{base: http.DefaultTransport, target: srv.URL}}
|
|
|
|
err := c.CreateRelease(context.Background(), "v1.0.0", "body")
|
|
if err == nil {
|
|
t.Fatal("expected error for 500 response")
|
|
}
|
|
if !strings.Contains(err.Error(), "500") {
|
|
t.Errorf("error should mention status code: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateReleaseRequestFails(t *testing.T) {
|
|
c := New("tok", "owner/repo")
|
|
// Use a transport that always fails.
|
|
c.httpClient = &http.Client{Transport: alwaysFailTransport{}}
|
|
|
|
err := c.CreateRelease(context.Background(), "v1.0.0", "body")
|
|
if err == nil {
|
|
t.Fatal("expected error when HTTP request fails")
|
|
}
|
|
}
|
|
|
|
func TestCreateReleaseBadURL(t *testing.T) {
|
|
// A repo containing a null byte makes the URL unparseable by http.NewRequestWithContext.
|
|
c := New("tok", "owner/repo\x00bad")
|
|
|
|
err := c.CreateRelease(context.Background(), "v1.0.0", "body")
|
|
if err == nil {
|
|
t.Fatal("expected error for invalid URL")
|
|
}
|
|
}
|
|
|
|
// rewriteTransport redirects all requests to a test server URL.
|
|
type rewriteTransport struct {
|
|
base http.RoundTripper
|
|
target string
|
|
}
|
|
|
|
func (rt rewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
req2 := req.Clone(req.Context())
|
|
req2.URL.Scheme = "http"
|
|
req2.URL.Host = strings.TrimPrefix(rt.target, "http://")
|
|
return rt.base.RoundTrip(req2)
|
|
}
|
|
|
|
// alwaysFailTransport returns an error for every request.
|
|
type alwaysFailTransport struct{}
|
|
|
|
func (alwaysFailTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
|
return nil, &testTransportError{"connection refused"}
|
|
}
|
|
|
|
type testTransportError struct{ msg string }
|
|
|
|
func (e *testTransportError) Error() string { return e.msg }
|