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
96 changes: 96 additions & 0 deletions templates/cli/internal/cmd/fanout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package cmd

import (
"bytes"
"errors"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"slices"
"strings"
Expand Down Expand Up @@ -206,6 +208,100 @@ func TestPickerOffersOnlyRealActions(t *testing.T) {
assertResolves("pull", pullPromptOrder, pullValues)
}

func TestEverythingSkipsConsoleOnlyActionsUnderAnAPIKey(t *testing.T) {
for _, run := range []struct {
name string
call func(*cobra.Command, *[]string) error
}{
{"pull", func(command *cobra.Command, ran *[]string) error {
return runPull(command, []pullAction{
{Value: "settings", Run: refuseConsole},
{Value: "functions", Run: record(ran, "functions")},
}, true)
}},
{"push", func(command *cobra.Command, ran *[]string) error {
return runPushActions(command, []pushAction{
{Value: "settings", Run: refuseConsole},
{Value: "functions", Run: record(ran, "functions")},
}, true)
}},
} {
t.Run(run.name, func(t *testing.T) {
isolateGlobalAll(t)

command, out := captureCommand()
var ran []string

if err := run.call(command, &ran); err != nil {
t.Fatalf("`%s all` stopped at settings: %v", run.name, err)
}
if !slices.Contains(ran, "functions") {
t.Errorf("`%s all` never reached functions", run.name)
}
if !strings.Contains(out.String(), "Skipping settings") {
t.Errorf("the skipped settings were not reported:\n%s", out)
}
})
}
}

func TestEverythingStillStopsOnOtherErrors(t *testing.T) {
isolateGlobalAll(t)

var ran []string
failure := errors.New("boom")

err := runPushActions(&cobra.Command{}, []pushAction{
{Value: "settings", Run: func(*cobra.Command) error { return failure }},
{Value: "functions", Run: record(&ran, "functions")},
}, true)
if !errors.Is(err, failure) {
t.Errorf("err = %v, want the settings failure", err)
}
if len(ran) != 0 {
t.Errorf("ran %v after a failure", ran)
}
}

func TestAllUnderAnAPIKeyLeavesOutOnlySettings(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write([]byte(`{"total":0}`))
}))
t.Cleanup(server.Close)

for _, name := range []string{"pull", "push"} {
t.Run(name, func(t *testing.T) {
isolateGlobalAll(t)

preferencesWith(t, `{"current":"ci","ci":{"endpoint":"`+server.URL+`","key":"secret"}}`)
t.Setenv("XDG_CONFIG_HOME", "")
directory := t.TempDir()
inDirectory(t, directory)
if err := os.WriteFile(filepath.Join(directory, config.LocalFileName),
[]byte(`{"projectId":"probe","projectName":"probe"}`), 0o600); err != nil {
t.Fatal(err)
}

root := NewRootCommand()
root.SetArgs([]string{name, "all", "--force"})
out := &strings.Builder{}
root.SetOut(out)
root.SetErr(out)

if err := root.Execute(); err != nil {
t.Fatalf("`%s all` with an API key failed: %v\n%s", name, err, out)
}
if !strings.Contains(out.String(), "Skipping settings") {
t.Errorf("the skipped settings were not reported:\n%s", out)
}
})
}
}

func refuseConsole(*cobra.Command) error {
return ErrConsoleNeedsSession
}

func record(ran *[]string, value string) func(*cobra.Command) error {
return func(*cobra.Command) error {
*ran = append(*ran, value)
Expand Down
4 changes: 4 additions & 0 deletions templates/cli/internal/cmd/pullall.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ func runPull(command *cobra.Command, actions []pullAction, everything bool) erro
continue
}
if err := action.Run(command); err != nil {
if skipsConsoleOnly(command, action.Value, "pull", err) {
continue
}

return err
}
}
Expand Down
4 changes: 4 additions & 0 deletions templates/cli/internal/cmd/pushall.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ func runPushActions(command *cobra.Command, actions []pushAction, everything boo
continue
}
if err := action.Run(command); err != nil {
if skipsConsoleOnly(command, action.Value, "push", err) {
continue
}

return err
}
}
Expand Down
13 changes: 13 additions & 0 deletions templates/cli/internal/cmd/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/internal/client"
"github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/internal/config"
"github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/internal/jsonx"
"github.com/{{ sdk.gitUserName }}/{{ sdk.gitRepoName | caseDash }}/internal/output"
)

// Implements session handling and the `whoami` command.
Expand All @@ -29,6 +30,18 @@ var ErrConsoleNeedsSession = errors.New(
"(e.g. `" + app.ExecutableName + " push functions`), not console-only commands " +
"(e.g. `" + app.ExecutableName + " push settings`)")

func skipsConsoleOnly(command *cobra.Command, resource, verb string, err error) bool {
if !errors.Is(err, ErrConsoleNeedsSession) {
return false
}

output.Warn(command.OutOrStdout(),
"Skipping %s: it needs a signed-in session, and only an API key is configured. Run `%s login` to %s %s too.",
resource, app.ExecutableName, verb, resource)

return true
}

// preferences loads the user's global preferences.
func preferences() (*config.Global, error) {
path, err := config.GlobalPath(app.ExecutableName)
Expand Down
Loading