Skip to content

Commit 1df35b7

Browse files
committed
fix(cli): guard upgrade confirmation output
1 parent 105c7f7 commit 1df35b7

2 files changed

Lines changed: 64 additions & 3 deletions

File tree

internal/cli/upgrade_cli.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ func (c *CLI) runUpgrade(ctx context.Context, cmd *upgradeCmd) error {
1414
return NewCLIError(ExitNotWired, ErrNotWired)
1515
}
1616
if !cmd.Check && !cmd.Yes {
17-
if !c.interactiveInput() {
18-
return NewCLIError(ExitUsage, errors.New("interactive upgrade requires a terminal; pass --check or --yes"))
17+
if !c.interactiveInput() || !c.interactiveOutput() || !c.interactivePromptOutput() {
18+
return NewCLIError(ExitUsage, errors.New("interactive upgrade requires terminal input and visible output; pass --check or --yes"))
1919
}
20-
confirmed, err := c.confirmSetup("Install the latest global npm release")
20+
confirmed, err := c.confirmSetup("Check npm for the latest GitContribute release and apply an eligible managed update")
2121
if err != nil {
2222
return NewCLIError(ExitUsage, err)
2323
}

internal/cli/upgrade_cli_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package cli_test
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
"github.com/morluto/gitcontribute/internal/cli"
11+
"github.com/morluto/gitcontribute/internal/contracts"
12+
)
13+
14+
type fakeUpgradeService struct {
15+
*fakeService
16+
calls int
17+
opts contracts.UpgradeOptions
18+
}
19+
20+
func (s *fakeUpgradeService) Upgrade(_ context.Context, opts contracts.UpgradeOptions) (*contracts.UpgradeReport, error) {
21+
s.calls++
22+
s.opts = opts
23+
return &contracts.UpgradeReport{}, nil
24+
}
25+
26+
func TestUpgradeDoesNotPromptWhenStandardOutputIsRedirected(t *testing.T) {
27+
redirected, err := os.CreateTemp(t.TempDir(), "upgrade-stdout")
28+
if err != nil {
29+
t.Fatal(err)
30+
}
31+
defer func() { _ = redirected.Close() }()
32+
33+
service := &fakeUpgradeService{fakeService: &fakeService{}}
34+
var stderr bytes.Buffer
35+
c := cli.New(service, &fakeMCPRunner{}, redirected, &stderr)
36+
c.SetInput(strings.NewReader(""))
37+
err = c.Run(context.Background(), []string{"upgrade"})
38+
if err == nil || !strings.Contains(err.Error(), "terminal input and visible output") {
39+
t.Fatalf("error = %v", err)
40+
}
41+
if service.calls != 0 {
42+
t.Fatalf("upgrade service was called %d times", service.calls)
43+
}
44+
}
45+
46+
func TestUpgradeConsentDescribesCheckAndEligibleManagedUpdate(t *testing.T) {
47+
service := &fakeUpgradeService{fakeService: &fakeService{}}
48+
var stdout, stderr bytes.Buffer
49+
c := cli.New(service, &fakeMCPRunner{}, &stdout, &stderr)
50+
c.SetInput(strings.NewReader("n\n"))
51+
if err := c.Run(context.Background(), []string{"upgrade"}); err != nil {
52+
t.Fatal(err)
53+
}
54+
if service.calls != 0 {
55+
t.Fatalf("upgrade service was called %d times", service.calls)
56+
}
57+
output := stderr.String()
58+
if !strings.Contains(output, "Check npm for the latest GitContribute release and apply an eligible managed update?") || strings.Contains(output, "Install the latest global npm release") {
59+
t.Fatalf("consent prompt = %q", output)
60+
}
61+
}

0 commit comments

Comments
 (0)