Skip to content
Open
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
9 changes: 7 additions & 2 deletions api/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package api

import (
"errors"
"fmt"
"os"
"time"

"github.com/spf13/viper"
Expand Down Expand Up @@ -31,9 +34,11 @@ func Client(config jira.Config) *jira.Client {
config.APIToken = viper.GetString("api_token")
}
if config.APIToken == "" {
netrcConfig, _ := netrc.Read(config.Server, config.Login)
if netrcConfig != nil {
netrcConfig, err := netrc.Read(config.Server, config.Login)
if err == nil {
config.APIToken = netrcConfig.Password
} else if !errors.Is(err, netrc.ErrNetrcEntryNotFound) {
fmt.Fprintf(os.Stderr, "warning: netrc lookup failed: %v\n", err)
}
}
if config.APIToken == "" {
Expand Down
3 changes: 3 additions & 0 deletions pkg/netrc/netrc.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ func Read(machine string, login string) (*Entry, error) {
if err != nil {
return nil, err
}
if serverURL.Host == "" {
return nil, fmt.Errorf("netrc config: invalid machine URL %q: missing host", machine)
}

for _, line := range netrc {
if line.machine == serverURL.Host && line.login == login {
Expand Down
76 changes: 76 additions & 0 deletions pkg/netrc/netrc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package netrc

import (
"errors"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

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

cases := []struct {
name string
machine string
login string
wantErr bool
errContains string
}{
{
name: "absolute path URL parses but has empty host",
machine: "/some/path",
login: "user",
wantErr: true,
errContains: "missing host",
},
{
name: "bare hostname without scheme is invalid URI",
machine: "example.com",
login: "user",
wantErr: true,
errContains: "invalid",
},
{
name: "valid URL with host returns not-found for unknown entry",
machine: "https://no-such-host.jira-cli-test.invalid",
login: "user@example.com",
wantErr: true,
},
}

for _, tc := range cases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

entry, err := Read(tc.machine, tc.login)
require.Error(t, err)
assert.Nil(t, entry)

if tc.errContains != "" {
assert.Contains(t, err.Error(), tc.errContains)
}
})
}
}

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

_, err := Read("https://no-such-host.jira-cli-test.invalid", "user@example.com")
require.Error(t, err)
assert.True(t, errors.Is(err, ErrNetrcEntryNotFound), "expected ErrNetrcEntryNotFound, got: %v", err)
}

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

_, err := Read("/absolute/path", "user")
require.Error(t, err)
// Should NOT be ErrNetrcEntryNotFound — it's a distinct validation error
assert.False(t, errors.Is(err, ErrNetrcEntryNotFound))
assert.Contains(t, err.Error(), "missing host")
}
6 changes: 5 additions & 1 deletion pkg/tui/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/cli/safeexec"
"github.com/gdamore/tcell/v2"
"github.com/google/shlex"
"github.com/mattn/go-isatty"
"github.com/rivo/tview"

Expand Down Expand Up @@ -109,7 +110,10 @@ func PagerOut(out string) error {
return err
}

pa := strings.Split(pagerCmd, " ")
pa, err := shlex.Split(pagerCmd)
if err != nil || len(pa) == 0 {
return fmt.Errorf("invalid pager command %q: %w", pagerCmd, err)
}
pager, pagerArgs := pa[0], pa[1:]
if err := cmdExists(pager); err != nil {
return err
Expand Down
29 changes: 29 additions & 0 deletions pkg/tui/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package tui

import (
"os"
"runtime"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestColumnPadding(t *testing.T) {
Expand Down Expand Up @@ -123,6 +125,33 @@ func TestSplitText(t *testing.T) {
}
}

func TestPagerOut_InvalidShlexSyntax(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("pager not used on Windows")
}

t.Setenv("TERM", "xterm")
t.Setenv("JIRA_PAGER", `less "--unclosed`)

err := PagerOut("test output")
require.Error(t, err)
assert.Contains(t, err.Error(), "invalid pager command")
}

func TestPagerOut_QuotedArgs(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("pager not used on Windows")
}

// "cat" with a quoted (but valid) argument string — shlex should parse it
// without error and the command should run successfully.
t.Setenv("TERM", "xterm")
t.Setenv("JIRA_PAGER", `cat`)

err := PagerOut("test output")
assert.NoError(t, err)
}

func TestGetPager(t *testing.T) {
// TERM is xterm, JIRA_PAGER is not set, PAGER is set.
{
Expand Down