Skip to content

Commit 26aeadb

Browse files
authored
cmd: add auth status command (#44)
## Description Adds `heygen auth status` — verifies the stored API key is valid by calling `GET /v3/user/me` and outputting account info. ```bash # Valid key $ heygen auth status {"data":{"email":"user@example.com","username":"demo",...}} # Invalid key → exit 3 $ heygen auth status {"error":{"code":"auth_error","message":"invalid API key"}} # No key configured → exit 3 $ heygen auth status {"error":{"code":"auth_error","message":"no API key found","hint":"Set HEYGEN_API_KEY env var or run: heygen auth login"}} ``` **skipAuth restructured:** Moved `skipAuth` annotation from the `auth` group to `auth login` specifically. `auth status` intentionally resolves auth — if no key is configured, it fails with the standard auth error (exit 3) before the API call. `auth login` continues to skip auth as before. **Reuses generated spec:** Calls `ctx.client.Execute(gen.UserMeGet, ...)` — same endpoint as `heygen user me get`. If the API path or response shape changes, both commands update together. **Auth vs server errors:** Distinguishable by exit code. Invalid key → exit 3 (`auth_error`). Server down → exit 1 (`server_error`/`network_error`). Retry transport handles transient failures before surfacing. Stacked on PR #43 (SKILL.md). ## Testing 3 tests: success (200 → exit 0, stdout has user email), invalid key (401 → exit 3, stderr has auth_error), no key (exit 3 with hint to set env var or run auth login). Existing `TestAuthLogin_SkipsAuth` continues passing — skipAuth annotation moved to login, not removed. All tests use `httptest.Server` — no real API calls.
1 parent 6d112c3 commit 26aeadb

4 files changed

Lines changed: 113 additions & 5 deletions

File tree

cmd/heygen/auth.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import "github.com/spf13/cobra"
44

55
func newAuthCmd(ctx *cmdContext) *cobra.Command {
66
cmd := &cobra.Command{
7-
Use: "auth",
8-
Short: "Manage authentication",
9-
Annotations: map[string]string{"skipAuth": "true"},
7+
Use: "auth",
8+
Short: "Manage authentication",
109
}
1110
cmd.AddCommand(newAuthLoginCmd(ctx))
11+
cmd.AddCommand(newAuthStatusCmd(ctx))
1212
return cmd
1313
}

cmd/heygen/auth_login.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import (
1616

1717
func newAuthLoginCmd(ctx *cmdContext) *cobra.Command {
1818
return &cobra.Command{
19-
Use: "login",
20-
Short: "Store API key for CLI authentication",
19+
Use: "login",
20+
Short: "Store API key for CLI authentication",
21+
Annotations: map[string]string{"skipAuth": "true"},
2122
Long: `Reads an API key from stdin and stores it for future CLI use.
2223
2324
Interactive:

cmd/heygen/auth_status.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package main
2+
3+
import (
4+
"net/url"
5+
6+
"github.com/heygen-com/heygen-cli/gen"
7+
"github.com/heygen-com/heygen-cli/internal/client"
8+
"github.com/heygen-com/heygen-cli/internal/command"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func newAuthStatusCmd(ctx *cmdContext) *cobra.Command {
13+
return &cobra.Command{
14+
Use: "status",
15+
Short: "Verify stored API key and show account info",
16+
Example: "heygen auth status",
17+
Args: cobra.NoArgs,
18+
RunE: func(cmd *cobra.Command, args []string) error {
19+
result, err := ctx.client.Execute(gen.UserMeGet, &command.Invocation{
20+
PathParams: make(map[string]string),
21+
QueryParams: make(url.Values),
22+
})
23+
if err != nil {
24+
return err
25+
}
26+
return ctx.formatter.Data(result, client.APIDataField, nil)
27+
},
28+
}
29+
}

cmd/heygen/auth_status_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
"testing"
7+
8+
clierrors "github.com/heygen-com/heygen-cli/internal/errors"
9+
)
10+
11+
func TestAuthStatus_Success(t *testing.T) {
12+
srv := setupTestServer(t, map[string]testHandler{
13+
"GET /v3/user/me": {
14+
StatusCode: 200,
15+
Body: `{"data":{"email":"user@example.com","username":"demo"}}`,
16+
},
17+
})
18+
defer srv.Close()
19+
20+
res := runCommand(t, srv.URL, "test-key", "auth", "status")
21+
22+
if res.ExitCode != 0 {
23+
t.Fatalf("ExitCode = %d, want 0\nstderr: %s", res.ExitCode, res.Stderr)
24+
}
25+
if res.Stderr != "" {
26+
t.Fatalf("stderr = %q, want empty", res.Stderr)
27+
}
28+
29+
var parsed map[string]any
30+
if err := json.Unmarshal([]byte(res.Stdout), &parsed); err != nil {
31+
t.Fatalf("stdout is not valid JSON: %v\nstdout: %s", err, res.Stdout)
32+
}
33+
data, ok := parsed["data"].(map[string]any)
34+
if !ok {
35+
t.Fatalf("data field missing or not object: %v", parsed)
36+
}
37+
if data["email"] != "user@example.com" {
38+
t.Fatalf("data.email = %v, want %q", data["email"], "user@example.com")
39+
}
40+
}
41+
42+
func TestAuthStatus_InvalidKey(t *testing.T) {
43+
srv := setupTestServer(t, map[string]testHandler{
44+
"GET /v3/user/me": {
45+
StatusCode: 401,
46+
Body: `{"error":{"message":"invalid API key"}}`,
47+
},
48+
})
49+
defer srv.Close()
50+
51+
res := runCommand(t, srv.URL, "invalid-key", "auth", "status")
52+
53+
if res.ExitCode != clierrors.ExitAuth {
54+
t.Fatalf("ExitCode = %d, want %d\nstderr: %s", res.ExitCode, clierrors.ExitAuth, res.Stderr)
55+
}
56+
if !strings.Contains(res.Stderr, `"code":"auth_error"`) {
57+
t.Fatalf("stderr = %s, want auth_error code", res.Stderr)
58+
}
59+
if !strings.Contains(res.Stderr, `"message":"invalid API key"`) {
60+
t.Fatalf("stderr = %s, want invalid API key message", res.Stderr)
61+
}
62+
}
63+
64+
func TestAuthStatus_NoKey(t *testing.T) {
65+
t.Setenv("HEYGEN_CONFIG_DIR", t.TempDir())
66+
67+
res := runCommand(t, "http://example.invalid", "", "auth", "status")
68+
69+
if res.ExitCode != clierrors.ExitAuth {
70+
t.Fatalf("ExitCode = %d, want %d\nstderr: %s", res.ExitCode, clierrors.ExitAuth, res.Stderr)
71+
}
72+
if !strings.Contains(res.Stderr, `"message":"no API key found"`) {
73+
t.Fatalf("stderr = %s, want missing API key message", res.Stderr)
74+
}
75+
if !strings.Contains(res.Stderr, `"hint":"Set HEYGEN_API_KEY env var or run: heygen auth login"`) {
76+
t.Fatalf("stderr = %s, want auth hint", res.Stderr)
77+
}
78+
}

0 commit comments

Comments
 (0)