gitlink-cli/cmd/auth/auth_test.go

244 lines
5.9 KiB
Go

package auth
import (
"errors"
"os"
"path/filepath"
"testing"
"github.com/spf13/cobra"
"github.com/zalando/go-keyring"
internalAuth "github.com/gitlink-org/gitlink-cli/internal/auth"
)
func tempConfigDir(t *testing.T) string {
t.Helper()
dir := t.TempDir()
t.Setenv("GITLINK_CONFIG_DIR", dir)
return dir
}
func TestEnvTokenVar(t *testing.T) {
if envTokenVar != "GITLINK_TOKEN" {
t.Fatalf("envTokenVar = %q, want GITLINK_TOKEN", envTokenVar)
}
}
func TestNewAuthCmd(t *testing.T) {
cmd := NewAuthCmd()
if cmd.Use != "auth" {
t.Fatalf("Use = %q, want auth", cmd.Use)
}
if cmd.Short == "" {
t.Fatal("Short is empty")
}
expectedSubs := map[string]bool{
"login": false, "logout": false, "status": false, "checkin": false,
}
for _, sub := range cmd.Commands() {
if _, ok := expectedSubs[sub.Use]; !ok {
t.Fatalf("unexpected subcommand: %q", sub.Use)
}
if expectedSubs[sub.Use] {
t.Fatalf("duplicate subcommand: %q", sub.Use)
}
expectedSubs[sub.Use] = true
if sub.Short == "" {
t.Fatalf("subcommand %q has empty Short", sub.Use)
}
}
for name, found := range expectedSubs {
if !found {
t.Fatalf("missing subcommand: %q", name)
}
}
}
func TestLoginTokenFlag(t *testing.T) {
cmd := NewAuthCmd()
loginCmd := findSub(cmd, "login")
if loginCmd == nil {
t.Fatal("login subcommand not found")
}
if f := loginCmd.Flags().Lookup("token"); f == nil {
t.Fatal("login command missing --token flag")
}
}
func TestStatusCmdNotLoggedIn(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
_ = internalAuth.DeleteToken()
cmd := findSub(NewAuthCmd(), "status")
if cmd == nil {
t.Fatal("status subcommand not found")
}
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatalf("status error: %v", err)
}
}
func TestStatusCmdEnvToken(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "env-token-123")
_ = internalAuth.DeleteToken()
cmd := findSub(NewAuthCmd(), "status")
cmd.RunE(cmd, nil)
}
func TestStatusCmdStoredToken(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
dir := tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
os.MkdirAll(dir, 0700)
os.WriteFile(filepath.Join(dir, "credentials"), []byte("cookie:test=abc"), 0600)
cmd := findSub(NewAuthCmd(), "status")
cmd.RunE(cmd, nil)
}
func TestStatusCmdEnvAndStoredToken(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
dir := tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "env-token")
os.MkdirAll(dir, 0700)
os.WriteFile(filepath.Join(dir, "credentials"), []byte("stored-token"), 0600)
cmd := findSub(NewAuthCmd(), "status")
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatalf("status error: %v", err)
}
}
func TestStatusCmdStoredTokenButLoadFails(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
// Do not create credentials; LoadToken should return empty.
cmd := findSub(NewAuthCmd(), "status")
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatalf("status error: %v", err)
}
}
func TestLogoutCmdNoStoredToken(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
// Do not create credentials; logout should be idempotent.
cmd := findSub(NewAuthCmd(), "logout")
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatalf("logout should succeed without stored token: %v", err)
}
}
func TestLogoutCmd(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
dir := tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
// Store a token first so DeleteToken has something to delete
os.MkdirAll(dir, 0700)
os.WriteFile(filepath.Join(dir, "credentials"), []byte("some-token"), 0600)
cmd := findSub(NewAuthCmd(), "logout")
if cmd == nil {
t.Fatal("logout subcommand not found")
}
if err := cmd.RunE(cmd, nil); err != nil {
t.Fatalf("logout error: %v", err)
}
}
func TestLoginWithToken(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
dir := tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
// Mock stdin
oldStdin := os.Stdin
r, w, _ := os.Pipe()
os.Stdin = r
defer func() { os.Stdin = oldStdin }()
go func() {
w.Write([]byte("test-token-123\n"))
w.Close()
}()
cmd := findSub(NewAuthCmd(), "login")
if cmd == nil {
t.Fatal("login subcommand not found")
}
cmd.Flags().Set("token", "true")
err := cmd.RunE(cmd, nil)
if err != nil {
t.Fatalf("login --token error: %v", err)
}
// Verify token was saved to file
data, err := os.ReadFile(filepath.Join(dir, "credentials"))
if err != nil {
t.Fatalf("read credentials: %v", err)
}
if string(data) != "test-token-123" {
t.Fatalf("token = %q, want test-token-123", string(data))
}
}
func TestLoginWithTokenEmpty(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
oldStdin := os.Stdin
r, w, _ := os.Pipe()
os.Stdin = r
defer func() { os.Stdin = oldStdin }()
go func() {
w.Write([]byte("\n"))
w.Close()
}()
cmd := findSub(NewAuthCmd(), "login")
cmd.Flags().Set("token", "true")
err := cmd.RunE(cmd, nil)
if err == nil {
t.Fatal("expected error for empty token")
}
}
func TestLoginWithPasswordNoTerminal(t *testing.T) {
keyring.MockInitWithError(errors.New("keychain unavailable"))
tempConfigDir(t)
t.Setenv("GITLINK_TOKEN", "")
// term.ReadPassword will fail because test has no terminal
cmd := findSub(NewAuthCmd(), "login")
// Don't set --token, so it goes to loginWithPassword
err := cmd.RunE(cmd, nil)
if err == nil {
t.Fatal("expected error when terminal unavailable (ReadPassword fails)")
}
}
func findSub(cmd *cobra.Command, name string) *cobra.Command {
for _, sub := range cmd.Commands() {
if sub.Use == name {
return sub
}
}
return nil
}