Skip to content

Commit 1d09050

Browse files
authored
chore(loo-4747): cmd tests (#9)
1 parent 22abd4b commit 1d09050

14 files changed

Lines changed: 274 additions & 43 deletions

cmd/api_key.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ import (
88
"github.com/spf13/cobra"
99
)
1010

11+
func runAPIKey(cfg *config.Config) (*api.APIKeyResponse, error) {
12+
return api.NewClient(cfg.EndpointURL, cfg.APIKey).GetAPIKey()
13+
}
14+
1115
var apiKeyCmd = &cobra.Command{
1216
Use: "api-key",
1317
Short: "Validate your API key",
@@ -17,16 +21,15 @@ var apiKeyCmd = &cobra.Command{
1721
return err
1822
}
1923

20-
client := api.NewClient(cfg.EndpointURL, cfg.APIKey)
21-
result, err := client.GetAPIKey()
24+
result, err := runAPIKey(cfg)
2225
if err != nil {
2326
return err
2427
}
2528

2629
if isJSONOutput() {
27-
return printJSON(result)
30+
return printJSON(cmd.OutOrStdout(), result)
2831
}
29-
fmt.Printf("Valid API key for team: %s\n", result.TeamName)
32+
fmt.Fprintf(cmd.OutOrStdout(), "Valid API key for team: %s\n", result.TeamName)
3033
return nil
3134
},
3235
}

cmd/api_key_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmd
2+
3+
import (
4+
"net/http"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/loops-so/cli/internal/api"
9+
)
10+
11+
func TestRunAPIKey(t *testing.T) {
12+
t.Run("returns team name", func(t *testing.T) {
13+
serveJSON(t, http.StatusOK, `{"teamName":"Acme Corp"}`)
14+
result, err := runAPIKey(cfg(t))
15+
if err != nil {
16+
t.Fatalf("unexpected error: %v", err)
17+
}
18+
want := &api.APIKeyResponse{TeamName: "Acme Corp"}
19+
if !reflect.DeepEqual(result, want) {
20+
t.Errorf("got %+v, want %+v", result, want)
21+
}
22+
})
23+
24+
t.Run("returns error on api failure", func(t *testing.T) {
25+
serveJSON(t, http.StatusUnauthorized, `{"error":"Invalid API key"}`)
26+
_, err := runAPIKey(cfg(t))
27+
if err == nil {
28+
t.Fatal("expected error, got nil")
29+
}
30+
})
31+
}

cmd/auth_login.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,24 +27,30 @@ var loginCmd = &cobra.Command{
2727
return fmt.Errorf("API key cannot be empty")
2828
}
2929

30-
client := api.NewClient(config.EndpointURL(), apiKey)
31-
result, err := client.GetAPIKey()
30+
result, err := runAuthLogin(apiKey)
3231
if err != nil {
33-
return fmt.Errorf("API key verification failed: %w", err)
34-
}
35-
36-
if err := config.Save(apiKey); err != nil {
3732
return err
3833
}
3934

4035
if isJSONOutput() {
41-
return printJSON(Result{Success: true, Message: fmt.Sprintf("Authenticated as team: %s", result.TeamName)})
36+
return printJSON(cmd.OutOrStdout(), Result{Success: true, Message: fmt.Sprintf("Authenticated as team: %s", result.TeamName)})
4237
}
43-
fmt.Printf("API key saved. Authenticated as team: %s\n", result.TeamName)
38+
fmt.Fprintf(cmd.OutOrStdout(), "API key saved. Authenticated as team: %s\n", result.TeamName)
4439
return nil
4540
},
4641
}
4742

43+
func runAuthLogin(apiKey string) (*api.APIKeyResponse, error) {
44+
result, err := api.NewClient(config.EndpointURL(), apiKey).GetAPIKey()
45+
if err != nil {
46+
return nil, fmt.Errorf("API key verification failed: %w", err)
47+
}
48+
if err := config.Save(apiKey); err != nil {
49+
return nil, err
50+
}
51+
return result, nil
52+
}
53+
4854
func init() {
4955
authCmd.AddCommand(loginCmd)
5056
}

cmd/auth_login_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmd
2+
3+
import (
4+
"net/http"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/loops-so/cli/internal/api"
9+
)
10+
11+
func TestRunAuthLogin(t *testing.T) {
12+
t.Run("saves key and returns team name", func(t *testing.T) {
13+
serveJSON(t, http.StatusOK, `{"teamName":"Acme"}`)
14+
result, err := runAuthLogin("test-key")
15+
if err != nil {
16+
t.Fatalf("unexpected error: %v", err)
17+
}
18+
want := &api.APIKeyResponse{TeamName: "Acme"}
19+
if !reflect.DeepEqual(result, want) {
20+
t.Errorf("got %+v, want %+v", result, want)
21+
}
22+
})
23+
24+
t.Run("returns error on api failure", func(t *testing.T) {
25+
serveJSON(t, http.StatusUnauthorized, `{"error":"Invalid API key"}`)
26+
_, err := runAuthLogin("bad-key")
27+
if err == nil {
28+
t.Fatal("expected error, got nil")
29+
}
30+
})
31+
}

cmd/auth_logout.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,21 @@ var logoutCmd = &cobra.Command{
1111
Use: "logout",
1212
Short: "Remove stored Loops credentials",
1313
RunE: func(cmd *cobra.Command, args []string) error {
14-
if err := config.Delete(); err != nil {
14+
if err := runAuthLogout(); err != nil {
1515
return err
1616
}
1717
if isJSONOutput() {
18-
return printJSON(Result{Success: true})
18+
return printJSON(cmd.OutOrStdout(), Result{Success: true})
1919
}
20-
fmt.Println("Logged out.")
20+
fmt.Fprintln(cmd.OutOrStdout(), "Logged out.")
2121
return nil
2222
},
2323
}
2424

25+
func runAuthLogout() error {
26+
return config.Delete()
27+
}
28+
2529
func init() {
2630
authCmd.AddCommand(logoutCmd)
2731
}

cmd/auth_logout_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestRunAuthLogout(t *testing.T) {
8+
t.Run("succeeds", func(t *testing.T) {
9+
mockKeyring(t)
10+
if err := runAuthLogout(); err != nil {
11+
t.Fatalf("unexpected error: %v", err)
12+
}
13+
})
14+
}

cmd/auth_status.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,25 @@ var statusCmd = &cobra.Command{
1111
Use: "status",
1212
Short: "Print the resolved configuration",
1313
RunE: func(cmd *cobra.Command, args []string) error {
14-
cfg, err := config.Load()
14+
cfg, err := runAuthStatus()
1515
if err != nil {
1616
return err
1717
}
1818

1919
if isJSONOutput() {
20-
return printJSON(cfg)
20+
return printJSON(cmd.OutOrStdout(), cfg)
2121
}
2222

23-
fmt.Printf("API Key: %s\n", cfg.APIKey)
24-
fmt.Printf("Endpoint: %s\n", cfg.EndpointURL)
23+
fmt.Fprintf(cmd.OutOrStdout(), "API Key: %s\n", cfg.APIKey)
24+
fmt.Fprintf(cmd.OutOrStdout(), "Endpoint: %s\n", cfg.EndpointURL)
2525
return nil
2626
},
2727
}
2828

29+
func runAuthStatus() (*config.Config, error) {
30+
return config.Load()
31+
}
32+
2933
func init() {
3034
authCmd.AddCommand(statusCmd)
3135
}

cmd/auth_status_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cmd
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/zalando/go-keyring"
8+
)
9+
10+
func TestRunAuthStatus(t *testing.T) {
11+
t.Run("returns config", func(t *testing.T) {
12+
serveJSON(t, http.StatusOK, `{}`)
13+
cfg, err := runAuthStatus()
14+
if err != nil {
15+
t.Fatalf("unexpected error: %v", err)
16+
}
17+
if cfg.APIKey == "" {
18+
t.Error("expected APIKey to be set")
19+
}
20+
if cfg.EndpointURL == "" {
21+
t.Error("expected EndpointURL to be set")
22+
}
23+
})
24+
25+
t.Run("returns error when no key set", func(t *testing.T) {
26+
keyring.MockInit()
27+
_, err := runAuthStatus()
28+
if err == nil {
29+
t.Fatal("expected error, got nil")
30+
}
31+
})
32+
}

cmd/helpers_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package cmd
2+
3+
import (
4+
"net/http"
5+
"net/http/httptest"
6+
"testing"
7+
8+
"github.com/loops-so/cli/internal/config"
9+
"github.com/zalando/go-keyring"
10+
)
11+
12+
func cfg(t *testing.T) *config.Config {
13+
t.Helper()
14+
c, err := config.Load()
15+
if err != nil {
16+
t.Fatalf("config.Load: %v", err)
17+
}
18+
return c
19+
}
20+
21+
func mockKeyring(t *testing.T) {
22+
t.Helper()
23+
keyring.MockInit()
24+
}
25+
26+
func serveJSON(t *testing.T, status int, body string) {
27+
t.Helper()
28+
keyring.MockInit()
29+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
30+
w.WriteHeader(status)
31+
w.Write([]byte(body))
32+
}))
33+
t.Cleanup(srv.Close)
34+
t.Setenv("LOOPS_API_KEY", "test-key")
35+
t.Setenv("LOOPS_ENDPOINT_URL", srv.URL)
36+
}

cmd/output.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package cmd
33
import (
44
"encoding/json"
55
"fmt"
6-
"os"
6+
"io"
77
)
88

99
type outputFlag string
@@ -30,8 +30,8 @@ func isJSONOutput() bool {
3030
return outputFormat == "json"
3131
}
3232

33-
func printJSON(v any) error {
34-
enc := json.NewEncoder(os.Stdout)
33+
func printJSON(w io.Writer, v any) error {
34+
enc := json.NewEncoder(w)
3535
enc.SetIndent("", " ")
3636
return enc.Encode(v)
3737
}

0 commit comments

Comments
 (0)