Files
vikingowl a23eb6b92c style: gofmt drift from prior commits
Pure whitespace cleanup surfaced when 'make check' ran gofmt over the
tree. Mostly struct-field column alignment in internal/safety/banner.go
(SessionInfo) and the var(...) flag block in cmd/gnoma/main.go after
--dangerously-allow-anywhere was added without realignment. Verified
zero substantive changes via 'git diff --ignore-all-space
--ignore-blank-lines'.
2026-05-24 16:33:17 +02:00

229 lines
6.0 KiB
Go

package google
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"cloud.google.com/go/auth"
_ "somegit.dev/Owlibou/gnoma/internal/provider"
)
func TestTryLoadOAuthCredentials_Formats(t *testing.T) {
tmpDir := t.TempDir()
tests := []struct {
name string
data interface{}
expectError bool
checkToken string
checkExpiry time.Time
}{
{
name: "snake_case and seconds expiry",
data: oauthCreds{
AccessToken: "token-snake",
ExpiryDate: time.Now().Add(1 * time.Hour).Unix(),
TokenType: "Bearer",
},
expectError: false,
checkToken: "token-snake",
},
{
name: "camelCase and milliseconds expiry",
data: oauthCreds{
AccessToken2: "token-camel",
ExpiresAt: time.Now().Add(1*time.Hour).UnixNano() / 1e6,
TokenType2: "Bearer",
},
expectError: false,
checkToken: "token-camel",
},
{
name: "expired token",
data: oauthCreds{
AccessToken: "token-expired",
ExpiryDate: time.Now().Add(-1 * time.Hour).Unix(),
},
expectError: true,
},
{
name: "missing access token",
data: oauthCreds{
ExpiryDate: time.Now().Add(1 * time.Hour).Unix(),
},
expectError: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
filePath := filepath.Join(tmpDir, "creds.json")
bz, err := json.Marshal(tc.data)
if err != nil {
t.Fatalf("marshal failed: %v", err)
}
if err := os.WriteFile(filePath, bz, 0644); err != nil {
t.Fatalf("write file failed: %v", err)
}
creds, err := tryLoadOAuthCredentials(filePath)
if tc.expectError {
if err == nil {
t.Fatalf("expected error but got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
tok, err := creds.Token(context.Background())
if err != nil {
t.Fatalf("failed to get token: %v", err)
}
if tok.Value != tc.checkToken {
t.Errorf("expected token %q, got %q", tc.checkToken, tok.Value)
}
})
}
}
func TestSelectOAuthCredentials_Precedence(t *testing.T) {
// Override HOME so expandHome() resolves into a sandbox dir.
tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)
writeCreds := func(relPath, tokenVal string) {
absPath := filepath.Join(tmpHome, relPath)
if err := os.MkdirAll(filepath.Dir(absPath), 0755); err != nil {
t.Fatalf("mkdir: %v", err)
}
data := oauthCreds{
AccessToken: tokenVal,
ExpiryDate: time.Now().Add(1 * time.Hour).Unix(),
}
bz, err := json.Marshal(data)
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(absPath, bz, 0600); err != nil {
t.Fatalf("write: %v", err)
}
}
tokenOf := func(c *auth.Credentials) string {
t.Helper()
tok, err := c.Token(context.Background())
if err != nil {
t.Fatalf("Token: %v", err)
}
return tok.Value
}
t.Run("agy beats gemini when both present", func(t *testing.T) {
// Fresh sandbox per subtest to avoid leftover files.
sub := t.TempDir()
t.Setenv("HOME", sub)
// Use the first agy path and the first gemini path.
writeAt := func(rel, tok string) {
abs := filepath.Join(sub, rel)
if err := os.MkdirAll(filepath.Dir(abs), 0755); err != nil {
t.Fatal(err)
}
bz, _ := json.Marshal(oauthCreds{
AccessToken: tok,
ExpiryDate: time.Now().Add(time.Hour).Unix(),
})
if err := os.WriteFile(abs, bz, 0600); err != nil {
t.Fatal(err)
}
}
writeAt(filepath.Join(".config", "google-antigravity", "session.json"), "token-agy")
writeAt(filepath.Join(".gemini", "oauth_creds.json"), "token-gemini")
creds, source, err := selectOAuthCredentials()
if err != nil {
t.Fatalf("selectOAuthCredentials: %v", err)
}
if source != CredentialSourceAgy {
t.Errorf("source = %q, want %q", source, CredentialSourceAgy)
}
if got := tokenOf(creds); got != "token-agy" {
t.Errorf("loaded token = %q, want token-agy (agy precedence violated)", got)
}
})
t.Run("falls back to gemini when agy missing", func(t *testing.T) {
sub := t.TempDir()
t.Setenv("HOME", sub)
// Only gemini file present.
geminiPath := filepath.Join(sub, ".gemini", "oauth_creds.json")
if err := os.MkdirAll(filepath.Dir(geminiPath), 0755); err != nil {
t.Fatal(err)
}
bz, _ := json.Marshal(oauthCreds{
AccessToken: "token-gemini-only",
ExpiryDate: time.Now().Add(time.Hour).Unix(),
})
if err := os.WriteFile(geminiPath, bz, 0600); err != nil {
t.Fatal(err)
}
creds, source, err := selectOAuthCredentials()
if err != nil {
t.Fatalf("selectOAuthCredentials: %v", err)
}
if source != CredentialSourceGemini {
t.Errorf("source = %q, want %q", source, CredentialSourceGemini)
}
if got := tokenOf(creds); got != "token-gemini-only" {
t.Errorf("loaded token = %q, want token-gemini-only", got)
}
})
t.Run("missing files are not warning-worthy", func(t *testing.T) {
// Sanity check: empty home directory walks the chain without
// failing in unexpected ways (only ADC would remain, which we
// don't assert on here because the test host may or may not have
// gcloud configured).
sub := t.TempDir()
t.Setenv("HOME", sub)
_, _, err := selectOAuthCredentials()
// Either ADC works on this host (no error) or no creds anywhere
// (returns our specific "no google credentials" error). Both are
// fine; the point is we don't panic or report a misconfiguration.
if err != nil && !strings.Contains(err.Error(), "no google credentials") {
t.Errorf("unexpected error shape: %v", err)
}
})
_ = writeCreds // keep helper available if extended in future
}
func TestFileTokenProvider_RejectsExpired(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "creds.json")
bz, _ := json.Marshal(oauthCreds{
AccessToken: "stale",
ExpiryDate: time.Now().Add(-time.Hour).Unix(),
})
if err := os.WriteFile(path, bz, 0600); err != nil {
t.Fatal(err)
}
tp := &fileTokenProvider{filePath: path}
tok, err := tp.Token(context.Background())
if err == nil {
t.Errorf("expected error for expired token, got token %+v", tok)
}
if err != nil && !strings.Contains(err.Error(), "expired") {
t.Errorf("error %q should mention expiry", err)
}
}