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
34 changes: 26 additions & 8 deletions internal/pcurl/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,42 @@ type AddOptions struct {
}

func confirmExisting(profileName, host string, c *Config, prompt *Prompter, force bool) (existing *Profile, declined bool, err error) {
existing = c.FindProfile(profileName)
if existing == nil {
if name := c.FindProfileByHost(host); name != "" {
existing = c.FindProfile(name)
if existing = c.FindProfile(profileName); existing != nil {
if force {
return existing, false, nil
}
}

if existing != nil && !force {
ok, promptErr := prompt.ConfirmUpdate(profileName)
if promptErr != nil {
return nil, false, promptErr
}
if !ok {
return existing, true, nil
}
return existing, false, nil
}

// No profile named profileName: check whether another profile already
// covers this host. (otherName == profileName is unreachable here — such a
// profile would have matched FindProfile above — but kept for clarity.)
otherName := c.FindProfileByHost(host)
if otherName == "" || otherName == profileName {
return nil, false, nil
}

return existing, false, nil
// --force creates the new profile without prompting; the existing
// same-host profile is left untouched.
if force {
return nil, false, nil
}

ok, promptErr := prompt.ConfirmHostConflict(profileName, otherName, host)
if promptErr != nil {
return nil, false, promptErr
}
if !ok {
return nil, true, nil
}
return nil, false, nil
}

func pickHeaders(parsed *curlparse.Result, opts AddOptions, prompt *Prompter) error {
Expand Down
40 changes: 40 additions & 0 deletions internal/pcurl/add_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,46 @@ func TestAdd_UpdateExisting_Decline(t *testing.T) {
assert.Equal(t, []string{"Accept: old"}, p.Headers, "headers should be unchanged")
}

func TestAdd_HostConflict_Confirm(t *testing.T) {
ex, _ := newTestExecuter(t)
c := &Config{Profiles: map[string]*Profile{
"example.com": {MatchHosts: []string{"example.com"}, Headers: []string{"Accept: old"}},
}}
require.NoError(t, ex.CM.Save(c))
var out bytes.Buffer

err := ex.Add(
[]string{"https://example.com", "-H", "Authorization: Bearer new"},
AddOptions{Name: "alt-account"},
&out, strings.NewReader("y\nk\n"), false,
)
require.NoError(t, err)

c, _ = ex.CM.Load()
assert.NotNil(t, c.FindProfile("example.com"), "original profile should remain")
assert.NotNil(t, c.FindProfile("alt-account"), "new profile should be created")
assert.Contains(t, out.String(), "already used by profile")
}

func TestAdd_HostConflict_Decline(t *testing.T) {
ex, _ := newTestExecuter(t)
c := &Config{Profiles: map[string]*Profile{
"example.com": {MatchHosts: []string{"example.com"}, Headers: []string{"Accept: old"}},
}}
require.NoError(t, ex.CM.Save(c))
var out bytes.Buffer

err := ex.Add(
[]string{"https://example.com", "-H", "Authorization: Bearer new"},
AddOptions{Name: "alt-account"},
&out, strings.NewReader("n\n"), false,
)
require.NoError(t, err)

c, _ = ex.CM.Load()
assert.Nil(t, c.FindProfile("alt-account"), "new profile should not be created")
}

func TestAdd_WithCookies_Force(t *testing.T) {
ex, kc := newTestExecuter(t)
var out bytes.Buffer
Expand Down
31 changes: 31 additions & 0 deletions internal/pcurl/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,37 @@ func (p *Prompter) confirmUpdateText(profileName string) (bool, error) {
return strings.ToLower(strings.TrimSpace(answer)) != "n", nil
}

// ConfirmHostConflict asks whether to create a new profile when the host
// is already covered by a differently-named profile.
func (p *Prompter) ConfirmHostConflict(newName, existingName, host string) (bool, error) {
if p.Interactive {
return p.confirmHostConflictHuh(newName, existingName, host)
}
return p.confirmHostConflictText(newName, existingName, host)
}

func (p *Prompter) confirmHostConflictHuh(newName, existingName, host string) (bool, error) {
var confirm bool
err := huh.NewConfirm().
Title(fmt.Sprintf("Host %q is already used by profile %q. Create new profile %q anyway?", host, existingName, newName)).
Affirmative("Yes").
Negative("No").
Value(&confirm).
Run()
return confirm, err
}

func (p *Prompter) confirmHostConflictText(newName, existingName, host string) (bool, error) {
fmt.Fprintf(p.Out, "Host %q is already used by profile %q. Create new profile %q anyway? [y/N]: ", host, existingName, newName)

answer, err := p.readLine()
if err != nil {
return false, err
}

return strings.ToLower(strings.TrimSpace(answer)) == "y", nil
}

// PickHeaders shows non-secret headers and lets the user toggle selection.
func (p *Prompter) PickHeaders(headers []curlparse.Header) error {
if p.Interactive {
Expand Down