Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion shared/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,27 @@ func New(baseURL, email, apiToken string, opts *Options) *Client {
BaseURL: baseURL,
AuthHeader: authHeader,
HTTPClient: &http.Client{
Timeout: timeout,
Timeout: timeout,
Transport: newTransport(),
},
Verbose: verbose,
VerboseOut: verboseOut,
}
}

// newTransport returns a connection pool owned by one Client. Sharing
// http.DefaultTransport lets unrelated code that calls its
// CloseIdleConnections (httptest.Server.Close does, for example) close a
// connection that net/http has already returned to the idle pool while the
// response for a bodiless request (such as a 204) is still being handed to
// the caller, which fails that request with "CloseIdleConnections called".
func newTransport() http.RoundTripper {
if t, ok := http.DefaultTransport.(*http.Transport); ok {
return t.Clone()
}
return http.DefaultTransport
}

// Do executes an HTTP request with the given method, path, and optional body.
//
// The path can be either relative to the BaseURL (e.g., "/rest/api/3/issue")
Expand Down
13 changes: 13 additions & 0 deletions shared/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ func TestNew(t *testing.T) {
t.Error("VerboseOut not set correctly")
}
})

t.Run("owns its transport", func(t *testing.T) {
t.Parallel()
a := New("https://example.atlassian.net", "user@example.com", "token", nil)
b := New("https://example.atlassian.net", "user@example.com", "token", nil)

if a.HTTPClient.Transport == nil || a.HTTPClient.Transport == http.DefaultTransport {
t.Error("Transport should not be http.DefaultTransport")
}
if a.HTTPClient.Transport == b.HTTPClient.Transport {
t.Error("each Client should have its own Transport")
}
})
}

func TestClient_Do(t *testing.T) {
Expand Down
23 changes: 16 additions & 7 deletions tools/jtk/internal/cmd/issues/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ import (
"github.com/open-cli-collective/jira-ticket-cli/internal/cmd/root"
)

// requireDeleted fails with the per-issue errors runDelete wrote to stderr,
// since its returned error only carries the failure count.
func requireDeleted(t *testing.T, err error, stderr *bytes.Buffer) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v\nstderr:\n%s", err, stderr.String())
}
}

func TestRunDelete_SingleIssue(t *testing.T) {
t.Parallel()

Expand All @@ -32,7 +41,7 @@ func TestRunDelete_SingleIssue(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-123"}, true)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Equal(t, stdout.String(), "Deleted PROJ-123\n")
testutil.Equal(t, stderr.String(), "")
}
Expand All @@ -54,7 +63,7 @@ func TestRunDelete_MultipleIssues(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-1", "PROJ-2", "PROJ-3"}, true)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Equal(t, stdout.String(), "Deleted PROJ-1\nDeleted PROJ-2\nDeleted PROJ-3\n")
testutil.Equal(t, stderr.String(), "")
}
Expand Down Expand Up @@ -146,7 +155,7 @@ func TestRunDelete_NonInteractive_WithForce_Proceeds(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-123"}, true)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Equal(t, stdout.String(), "Deleted PROJ-123\n")
}

Expand All @@ -165,7 +174,7 @@ func TestRunDelete_PromptDeclined(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-123"}, false)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Equal(t, stdout.String(), "Deletion cancelled.\n")
testutil.Contains(t, stderr.String(), "permanently delete issue PROJ-123")
}
Expand All @@ -185,7 +194,7 @@ func TestRunDelete_BatchPromptDeclined(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-1", "PROJ-2", "PROJ-3"}, false)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Contains(t, stderr.String(), "3 issues")
testutil.Equal(t, stdout.String(), "Deletion cancelled.\n")
}
Expand All @@ -210,7 +219,7 @@ func TestRunDelete_PromptAccepted(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-123"}, false)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Equal(t, stdout.String(), "Deleted PROJ-123\n")
}

Expand All @@ -234,7 +243,7 @@ func TestRunDelete_BatchPromptAccepted(t *testing.T) {
opts.SetAPIClient(client)

err = runDelete(context.Background(), opts, []string{"PROJ-1", "PROJ-2"}, false)
testutil.RequireNoError(t, err)
requireDeleted(t, err, &stderr)
testutil.Equal(t, stdout.String(), "Deleted PROJ-1\nDeleted PROJ-2\n")
}

Expand Down
Loading