Skip to content

Commit c1ebd60

Browse files
Peter Steinbergersalmonumbrella
andcommitted
test: expand coverage and tracking regressions (openclaw#35)
Thanks @salmonumbrella. Co-authored-by: salmonumbrella <salmonumbrella@users.noreply.github.com>
1 parent 2fe888e commit c1ebd60

46 files changed

Lines changed: 4807 additions & 194 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
- Auth: `gog auth manage` stores tokens under the real account email (Google userinfo) (#36) — thanks @salmonumbrella.
4141
- Docs: `docs info`/`docs cat` now use the Docs API (Drive still used for exports/copy/create).
4242
- Calendar: feature-parity flags + new event types (focus-time/ooo/working-location) and recurring scope updates (#38) — thanks @salmonumbrella.
43+
- Tests: expand coverage and add tracking/gmail/calendar regressions (#35) — thanks @salmonumbrella.
4344

4445
## 0.4.2 - 2025-12-31
4546

internal/cmd/auth_more_test.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
11+
"github.com/steipete/gogcli/internal/config"
12+
"github.com/steipete/gogcli/internal/googleauth"
13+
"github.com/steipete/gogcli/internal/outfmt"
14+
"github.com/steipete/gogcli/internal/ui"
15+
)
16+
17+
func TestAuthKeepCmd_JSON(t *testing.T) {
18+
home := t.TempDir()
19+
t.Setenv("HOME", home)
20+
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg"))
21+
22+
keyPath := filepath.Join(t.TempDir(), "sa.json")
23+
if err := os.WriteFile(keyPath, []byte(`{"type":"service_account"}`), 0o600); err != nil {
24+
t.Fatalf("write key: %v", err)
25+
}
26+
27+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
28+
if err != nil {
29+
t.Fatalf("ui.New: %v", err)
30+
}
31+
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
32+
33+
cmd := AuthKeepCmd{Email: "a@b.com", Key: keyPath}
34+
out := captureStdout(t, func() {
35+
if err := cmd.Run(ctx); err != nil {
36+
t.Fatalf("AuthKeepCmd: %v", err)
37+
}
38+
})
39+
var payload struct {
40+
Stored bool `json:"stored"`
41+
Email string `json:"email"`
42+
Path string `json:"path"`
43+
}
44+
if err := json.Unmarshal([]byte(out), &payload); err != nil {
45+
t.Fatalf("decode output: %v", err)
46+
}
47+
if !payload.Stored || payload.Email != "a@b.com" || payload.Path == "" {
48+
t.Fatalf("unexpected payload: %#v", payload)
49+
}
50+
if _, err := os.Stat(payload.Path); err != nil {
51+
t.Fatalf("missing stored key: %v", err)
52+
}
53+
}
54+
55+
func TestAuthManageCmd(t *testing.T) {
56+
orig := startManageServer
57+
t.Cleanup(func() { startManageServer = orig })
58+
59+
var captured googleauth.ManageServerOptions
60+
startManageServer = func(_ context.Context, opts googleauth.ManageServerOptions) error {
61+
captured = opts
62+
return nil
63+
}
64+
65+
cmd := AuthManageCmd{ServicesCSV: "gmail,calendar", ForceConsent: true}
66+
if err := cmd.Run(context.Background()); err != nil {
67+
t.Fatalf("AuthManageCmd: %v", err)
68+
}
69+
if !captured.ForceConsent || len(captured.Services) != 2 {
70+
t.Fatalf("unexpected manage options: %#v", captured)
71+
}
72+
}
73+
74+
func TestAuthServicesCmd_Markdown(t *testing.T) {
75+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
76+
if err != nil {
77+
t.Fatalf("ui.New: %v", err)
78+
}
79+
ctx := ui.WithUI(context.Background(), u)
80+
81+
cmd := AuthServicesCmd{Markdown: true}
82+
out := captureStdout(t, func() {
83+
if err := cmd.Run(ctx); err != nil {
84+
t.Fatalf("AuthServicesCmd: %v", err)
85+
}
86+
})
87+
if !strings.Contains(out, "|") {
88+
t.Fatalf("expected markdown output, got: %q", out)
89+
}
90+
}
91+
92+
func TestAuthServicesCmd_JSON(t *testing.T) {
93+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
94+
if err != nil {
95+
t.Fatalf("ui.New: %v", err)
96+
}
97+
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
98+
99+
cmd := AuthServicesCmd{}
100+
out := captureStdout(t, func() {
101+
if err := cmd.Run(ctx); err != nil {
102+
t.Fatalf("AuthServicesCmd: %v", err)
103+
}
104+
})
105+
if !strings.Contains(out, "\"services\"") {
106+
t.Fatalf("unexpected json output: %q", out)
107+
}
108+
}
109+
110+
func TestAuthServicesCmd_Table(t *testing.T) {
111+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
112+
if err != nil {
113+
t.Fatalf("ui.New: %v", err)
114+
}
115+
ctx := ui.WithUI(context.Background(), u)
116+
117+
cmd := AuthServicesCmd{}
118+
out := captureStdout(t, func() {
119+
if err := cmd.Run(ctx); err != nil {
120+
t.Fatalf("AuthServicesCmd: %v", err)
121+
}
122+
})
123+
if !strings.Contains(out, "SERVICE") {
124+
t.Fatalf("unexpected table output: %q", out)
125+
}
126+
}
127+
128+
func TestAuthKeepCmd_Text(t *testing.T) {
129+
home := t.TempDir()
130+
t.Setenv("HOME", home)
131+
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg"))
132+
133+
keyPath := filepath.Join(t.TempDir(), "sa.json")
134+
if err := os.WriteFile(keyPath, []byte(`{"type":"service_account"}`), 0o600); err != nil {
135+
t.Fatalf("write key: %v", err)
136+
}
137+
138+
out := captureStdout(t, func() {
139+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
140+
if err != nil {
141+
t.Fatalf("ui.New: %v", err)
142+
}
143+
ctx := ui.WithUI(context.Background(), u)
144+
145+
cmd := AuthKeepCmd{Email: "a@b.com", Key: keyPath}
146+
if err := cmd.Run(ctx); err != nil {
147+
t.Fatalf("AuthKeepCmd: %v", err)
148+
}
149+
})
150+
if !strings.Contains(out, "Keep service account configured") {
151+
t.Fatalf("unexpected output: %q", out)
152+
}
153+
}
154+
155+
func TestAuthStatusCmd_JSON(t *testing.T) {
156+
home := t.TempDir()
157+
t.Setenv("HOME", home)
158+
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, "xdg"))
159+
160+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
161+
if err != nil {
162+
t.Fatalf("ui.New: %v", err)
163+
}
164+
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
165+
166+
if _, err := config.ConfigPath(); err != nil {
167+
t.Fatalf("ConfigPath: %v", err)
168+
}
169+
170+
cmd := AuthStatusCmd{}
171+
out := captureStdout(t, func() {
172+
if err := cmd.Run(ctx); err != nil {
173+
t.Fatalf("AuthStatusCmd: %v", err)
174+
}
175+
})
176+
if !strings.Contains(out, "\"keyring\"") || !strings.Contains(out, "\"config\"") {
177+
t.Fatalf("unexpected status output: %q", out)
178+
}
179+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"errors"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"github.com/steipete/gogcli/internal/outfmt"
14+
"github.com/steipete/gogcli/internal/secrets"
15+
"github.com/steipete/gogcli/internal/ui"
16+
)
17+
18+
func TestAuthTokensExportImport_JSON(t *testing.T) {
19+
origOpen := openSecretsStore
20+
origEnsure := ensureKeychainAccess
21+
t.Cleanup(func() {
22+
openSecretsStore = origOpen
23+
ensureKeychainAccess = origEnsure
24+
})
25+
26+
store := newMemStore()
27+
openSecretsStore = func() (secrets.Store, error) { return store, nil }
28+
ensureKeychainAccess = func() error { return nil }
29+
30+
tok := secrets.Token{
31+
Email: "a@b.com",
32+
RefreshToken: "rt",
33+
Services: []string{"gmail"},
34+
Scopes: []string{"s1"},
35+
CreatedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
36+
}
37+
if err := store.SetToken(tok.Email, tok); err != nil {
38+
t.Fatalf("SetToken: %v", err)
39+
}
40+
41+
outPath := filepath.Join(t.TempDir(), "token.json")
42+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
43+
if err != nil {
44+
t.Fatalf("ui.New: %v", err)
45+
}
46+
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
47+
48+
exportCmd := AuthTokensExportCmd{
49+
Email: tok.Email,
50+
Output: OutputPathRequiredFlag{Path: outPath},
51+
Overwrite: true,
52+
}
53+
if err := exportCmd.Run(ctx); err != nil {
54+
t.Fatalf("export: %v", err)
55+
}
56+
57+
data, err := os.ReadFile(outPath)
58+
if err != nil {
59+
t.Fatalf("read export: %v", err)
60+
}
61+
var payload map[string]any
62+
if err := json.Unmarshal(data, &payload); err != nil {
63+
t.Fatalf("parse export: %v", err)
64+
}
65+
if payload["refresh_token"] != "rt" {
66+
t.Fatalf("unexpected export payload: %#v", payload)
67+
}
68+
69+
// Import back into a fresh store.
70+
newStore := newMemStore()
71+
openSecretsStore = func() (secrets.Store, error) { return newStore, nil }
72+
73+
importCmd := AuthTokensImportCmd{InPath: outPath}
74+
if err := importCmd.Run(ctx); err != nil {
75+
t.Fatalf("import: %v", err)
76+
}
77+
imported, err := newStore.GetToken(tok.Email)
78+
if err != nil {
79+
t.Fatalf("GetToken: %v", err)
80+
}
81+
if imported.RefreshToken != "rt" {
82+
t.Fatalf("unexpected imported token: %#v", imported)
83+
}
84+
}
85+
86+
func TestAuthList_CheckJSON(t *testing.T) {
87+
origOpen := openSecretsStore
88+
origCheck := checkRefreshToken
89+
t.Cleanup(func() {
90+
openSecretsStore = origOpen
91+
checkRefreshToken = origCheck
92+
})
93+
94+
store := newMemStore()
95+
openSecretsStore = func() (secrets.Store, error) { return store, nil }
96+
checkRefreshToken = func(context.Context, string, []string, time.Duration) error {
97+
return nil
98+
}
99+
100+
if err := store.SetToken("a@b.com", secrets.Token{Email: "a@b.com", RefreshToken: "rt"}); err != nil {
101+
t.Fatalf("SetToken: %v", err)
102+
}
103+
104+
u, err := ui.New(ui.Options{Stdout: os.Stdout, Stderr: os.Stderr, Color: "never"})
105+
if err != nil {
106+
t.Fatalf("ui.New: %v", err)
107+
}
108+
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
109+
110+
listCmd := AuthListCmd{Check: true}
111+
out := captureStdout(t, func() {
112+
if err := listCmd.Run(ctx); err != nil {
113+
t.Fatalf("list: %v", err)
114+
}
115+
})
116+
var payload struct {
117+
Accounts []struct {
118+
Email string `json:"email"`
119+
Valid *bool `json:"valid"`
120+
} `json:"accounts"`
121+
}
122+
if err := json.Unmarshal([]byte(out), &payload); err != nil {
123+
t.Fatalf("decode list output: %v", err)
124+
}
125+
if len(payload.Accounts) != 1 || payload.Accounts[0].Email != "a@b.com" || payload.Accounts[0].Valid == nil || !*payload.Accounts[0].Valid {
126+
t.Fatalf("unexpected list payload: %#v", payload.Accounts)
127+
}
128+
}
129+
130+
type memStore struct {
131+
tokens map[string]secrets.Token
132+
defaultEmail string
133+
}
134+
135+
func newMemStore() *memStore {
136+
return &memStore{tokens: make(map[string]secrets.Token)}
137+
}
138+
139+
func (m *memStore) Keys() ([]string, error) {
140+
keys := make([]string, 0, len(m.tokens))
141+
for k := range m.tokens {
142+
keys = append(keys, "token:"+k)
143+
}
144+
return keys, nil
145+
}
146+
147+
func (m *memStore) SetToken(email string, tok secrets.Token) error {
148+
if strings.TrimSpace(email) == "" {
149+
return errors.New("missing email")
150+
}
151+
if strings.TrimSpace(tok.RefreshToken) == "" {
152+
return errors.New("missing refresh token")
153+
}
154+
m.tokens[email] = tok
155+
return nil
156+
}
157+
158+
func (m *memStore) GetToken(email string) (secrets.Token, error) {
159+
tok, ok := m.tokens[email]
160+
if !ok {
161+
return secrets.Token{}, errors.New("not found")
162+
}
163+
return tok, nil
164+
}
165+
166+
func (m *memStore) DeleteToken(email string) error {
167+
delete(m.tokens, email)
168+
return nil
169+
}
170+
171+
func (m *memStore) ListTokens() ([]secrets.Token, error) {
172+
out := make([]secrets.Token, 0, len(m.tokens))
173+
for _, tok := range m.tokens {
174+
out = append(out, tok)
175+
}
176+
return out, nil
177+
}
178+
179+
func (m *memStore) GetDefaultAccount() (string, error) {
180+
return m.defaultEmail, nil
181+
}
182+
183+
func (m *memStore) SetDefaultAccount(email string) error {
184+
m.defaultEmail = email
185+
return nil
186+
}

0 commit comments

Comments
 (0)