Skip to content

Commit ea4a980

Browse files
Merge pull request #60 from guergabo/unit-tests
test(unit): unit tests for switch command
2 parents d8d484e + aa62726 commit ea4a980

File tree

7 files changed

+180
-32
lines changed

7 files changed

+180
-32
lines changed

cli/login.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ If the browser does not open, please visit the following URL:
5454
}
5555

5656
if loginErr != nil {
57-
failure("Authentication failed. Please contact support at [email protected]")
57+
failure(cmd, "Authentication failed. Please contact support at [email protected]")
5858
fmt.Printf("Error: %s\n", loginErr)
5959
} else if loggedIn {
6060
success("Authentication successful")

cli/main.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,31 @@ import (
66
"github.com/spf13/cobra"
77
)
88

9-
// Main is the entry point of the command line.
10-
func Main() error {
11-
cmd := &cobra.Command{
12-
Version: version(),
13-
Use: "dispatch",
14-
Long: `Welcome to Dispatch!
9+
var (
10+
DispatchCmdLong = `Welcome to Dispatch!
1511
1612
To get started, use the login command to authenticate with Dispatch or create an account.
17-
13+
1814
Documentation: https://docs.dispatch.run
1915
Discord: https://dispatch.run/discord
2016
21-
`,
17+
`
18+
)
19+
20+
// Main is the entry point of the command line.
21+
func Main() error {
22+
cmd := &cobra.Command{
23+
Version: version(),
24+
Use: "dispatch",
25+
Long: DispatchCmdLong,
2226
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
2327
return loadEnvFromFile(DotEnvFilePath)
2428
},
2529
RunE: func(cmd *cobra.Command, args []string) error {
2630
return cmd.Help()
2731
},
2832
}
33+
2934
cmd.PersistentFlags().StringVarP(&DispatchApiKeyCli, "api-key", "k", "", "Dispatch API key (env: DISPATCH_API_KEY)")
3035
cmd.PersistentFlags().StringVarP(&DotEnvFilePath, "env-file", "", "", "Path to .env file")
3136

@@ -38,10 +43,12 @@ Support: [email protected]
3843
Title: "Dispatch Commands:",
3944
})
4045

46+
// Passing the global variables to the commands make testing in parallel possible.
4147
cmd.AddCommand(loginCommand())
42-
cmd.AddCommand(switchCommand())
43-
cmd.AddCommand(runCommand())
48+
cmd.AddCommand(switchCommand(DispatchConfigPath))
4449
cmd.AddCommand(verificationCommand())
50+
cmd.AddCommand(runCommand())
4551
cmd.AddCommand(versionCommand())
52+
4653
return cmd.ExecuteContext(context.Background())
4754
}

cli/style.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package cli
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/charmbracelet/bubbles/spinner"
78
tea "github.com/charmbracelet/bubbletea"
89
"github.com/charmbracelet/lipgloss"
10+
"github.com/spf13/cobra"
911
)
1012

1113
var (
@@ -110,12 +112,12 @@ func success(msg string) {
110112
fmt.Println(successStyle.Render(msg))
111113
}
112114

113-
func failure(msg string) {
114-
fmt.Println(failureStyle.Render(msg) + "\n")
115+
func failure(cmd *cobra.Command, msgs ...string) {
116+
cmd.Println(failureStyle.Render(strings.Join(msgs, " ")) + "\n")
115117
}
116118

117-
func simple(msg string) {
118-
fmt.Println(msg)
119+
func simple(cmd *cobra.Command, msgs ...string) {
120+
cmd.Println(strings.Join(msgs, " "))
119121
}
120122

121123
func dialog(msg string, args ...interface{}) {

cli/switch.go

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,38 @@ import (
88
"github.com/spf13/cobra"
99
)
1010

11-
func switchCommand() *cobra.Command {
12-
cmd := &cobra.Command{
13-
Use: "switch [organization]",
14-
Short: "Switch between organizations",
15-
Long: `Switch between Dispatch organizations.
11+
var (
12+
SwitchCmdLong = `Switch between Dispatch organizations.
1613
1714
The switch command is used to select which organization is used
1815
when running a Dispatch application locally.
16+
17+
To manage your organizations, visit the Dispatch Console: https://console.dispatch.run/`
18+
)
1919

20-
To manage your organizations, visit the Dispatch Console: https://console.dispatch.run/`,
20+
func switchCommand(configPath string) *cobra.Command {
21+
cmd := &cobra.Command{
22+
Use: "switch [organization]",
23+
Short: "Switch between organizations",
24+
Long: SwitchCmdLong,
2125
GroupID: "management",
2226
RunE: func(cmd *cobra.Command, args []string) error {
23-
cfg, err := LoadConfig(DispatchConfigPath)
27+
cfg, err := LoadConfig(configPath)
2428
if err != nil {
2529
if !errors.Is(err, os.ErrNotExist) {
26-
failure(fmt.Sprintf("Failed to load Dispatch configuration: %v", err))
30+
failure(cmd, fmt.Sprintf("Failed to load Dispatch configuration: %v", err))
2731
}
28-
simple("Please run `dispatch login` to login to Dispatch.")
32+
33+
// User must login to create a configuration file.
34+
simple(cmd, "Please run `dispatch login` to login to Dispatch.")
2935
return nil
3036
}
3137

3238
// List organizations if no arguments were provided.
3339
if len(args) == 0 {
34-
fmt.Println("Available organizations:")
40+
simple(cmd, "Available organizations:")
3541
for org := range cfg.Organization {
36-
fmt.Println("-", org)
42+
simple(cmd, "-", org)
3743
}
3844
return nil
3945
}
@@ -42,18 +48,18 @@ To manage your organizations, visit the Dispatch Console: https://console.dispat
4248
name := args[0]
4349
_, ok := cfg.Organization[name]
4450
if !ok {
45-
failure(fmt.Sprintf("Organization '%s' not found", name))
51+
failure(cmd, fmt.Sprintf("Organization '%s' not found", name))
4652

47-
fmt.Println("Available organizations:")
53+
simple(cmd, "Available organizations:")
4854
for org := range cfg.Organization {
49-
fmt.Println("-", org)
55+
simple(cmd, "-", org)
5056
}
5157
return nil
5258
}
5359

54-
simple(fmt.Sprintf("Switched to organization: %v", name))
60+
simple(cmd, fmt.Sprintf("Switched to organization: %v", name))
5561
cfg.Active = name
56-
return CreateConfig(DispatchConfigPath, cfg)
62+
return CreateConfig(configPath, cfg)
5763
},
5864
}
5965
return cmd

cli/switch_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package cli
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
type testCase struct {
13+
name string
14+
args []string
15+
configExists bool
16+
configContent string
17+
}
18+
19+
type expectedOutput struct {
20+
stdout string
21+
stderr string
22+
}
23+
24+
func TestSwitchCommand(t *testing.T) {
25+
tcs := []struct {
26+
in testCase
27+
out expectedOutput
28+
}{
29+
{
30+
in: testCase{
31+
name: "Config file doesn't exist",
32+
args: []string{"org1"},
33+
configExists: false,
34+
},
35+
out: expectedOutput{
36+
stdout: "Please run `dispatch login` to login to Dispatch.\n",
37+
},
38+
},
39+
{
40+
in: testCase{
41+
name: "No arguments provided",
42+
args: []string{},
43+
configExists: true,
44+
configContent: `
45+
# Warning = 'THIS FILE IS GENERATED. DO NOT EDIT!'
46+
active = 'x-s-org'
47+
48+
[Organizations]
49+
[Organizations.x-s-org]
50+
api_key = 'x'
51+
`,
52+
},
53+
out: expectedOutput{
54+
stdout: "Available organizations:\n- x-s-org\n",
55+
},
56+
},
57+
{
58+
in: testCase{
59+
name: "Switch to non-existing organization",
60+
args: []string{"random"},
61+
configExists: true,
62+
configContent: `
63+
# Warning = 'THIS FILE IS GENERATED. DO NOT EDIT!'
64+
active = 'x-s-org'
65+
66+
[Organizations]
67+
[Organizations.x-s-org]
68+
api_key = 'x'
69+
`,
70+
},
71+
out: expectedOutput{
72+
stdout: "Organization 'random' not found\n\nAvailable organizations:\n- x-s-org\n",
73+
},
74+
},
75+
{
76+
in: testCase{
77+
name: "Switch to existing organization",
78+
args: []string{"x-s-org"},
79+
configExists: true,
80+
configContent: `
81+
# Warning = 'THIS FILE IS GENERATED. DO NOT EDIT!'
82+
active = 'x-s-org'
83+
84+
[Organizations]
85+
[Organizations.x-s-org]
86+
api_key = 'x'
87+
`,
88+
},
89+
out: expectedOutput{
90+
stdout: "Switched to organization: x-s-org\n",
91+
},
92+
},
93+
}
94+
95+
for _, tc := range tcs {
96+
tc := tc
97+
t.Run(tc.in.name, func(t *testing.T) {
98+
t.Parallel()
99+
100+
configPath := setupConfig(t, tc.in)
101+
stdout := &bytes.Buffer{}
102+
stderr := &bytes.Buffer{}
103+
cmd := switchCommand(configPath)
104+
cmd.SetOut(stdout)
105+
cmd.SetErr(stderr)
106+
cmd.SetArgs(tc.in.args)
107+
108+
if err := cmd.Execute(); err != nil {
109+
t.Fatalf("Received unexpected error: %v", err)
110+
}
111+
112+
assert.Equal(t, tc.out.stdout, stdout.String())
113+
assert.Equal(t, tc.out.stderr, stderr.String())
114+
})
115+
}
116+
}
117+
118+
func setupConfig(t *testing.T, tc testCase) string {
119+
tempDir := t.TempDir() // unique temp dir for each test and cleaned up after test finishes
120+
configPath := filepath.Join(tempDir, "config.yaml")
121+
if tc.configExists {
122+
err := os.WriteFile(configPath, []byte(tc.configContent), 0600)
123+
assert.NoError(t, err)
124+
} else {
125+
os.Remove(configPath)
126+
}
127+
return configPath
128+
}

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ require (
77
github.com/charmbracelet/bubbles v0.18.0
88
github.com/charmbracelet/bubbletea v0.25.0
99
github.com/charmbracelet/lipgloss v0.9.1
10+
github.com/joho/godotenv v1.5.1
1011
github.com/muesli/reflow v0.3.0
1112
github.com/nlpodyssey/gopickle v0.3.0
1213
github.com/pelletier/go-toml/v2 v2.2.0
1314
github.com/spf13/cobra v1.8.0
15+
github.com/stretchr/testify v1.9.0
1416
golang.org/x/term v0.19.0
1517
google.golang.org/protobuf v1.33.0
1618
)
@@ -20,18 +22,20 @@ require (
2022
github.com/atotto/clipboard v0.1.4 // indirect
2123
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2224
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
25+
github.com/davecgh/go-spew v1.1.1 // indirect
2326
github.com/inconshreveable/mousetrap v1.1.0 // indirect
24-
github.com/joho/godotenv v1.5.1 // indirect
2527
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
2628
github.com/mattn/go-isatty v0.0.18 // indirect
2729
github.com/mattn/go-localereader v0.0.1 // indirect
2830
github.com/mattn/go-runewidth v0.0.15 // indirect
2931
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
3032
github.com/muesli/cancelreader v0.2.2 // indirect
3133
github.com/muesli/termenv v0.15.2 // indirect
34+
github.com/pmezard/go-difflib v1.0.0 // indirect
3235
github.com/rivo/uniseg v0.4.6 // indirect
3336
github.com/spf13/pflag v1.0.5 // indirect
3437
golang.org/x/sync v0.1.0 // indirect
3538
golang.org/x/sys v0.19.0 // indirect
3639
golang.org/x/text v0.14.0 // indirect
40+
gopkg.in/yaml.v3 v3.0.1 // indirect
3741
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
8181
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
8282
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
8383
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
84+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
8485
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
8586
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
8687
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

0 commit comments

Comments
 (0)