200 lines
6.2 KiB
Go
200 lines
6.2 KiB
Go
// auth_test.go 驗證認證來源選序(--token → TEAI_TOKEN → teai 組態)
|
||
// 與組態解析。
|
||
package gitea
|
||
|
||
import (
|
||
"errors"
|
||
"testing"
|
||
)
|
||
|
||
// fakeEnv 依 map 回傳環境變數。
|
||
func fakeEnv(m map[string]string) EnvLookup {
|
||
return func(key string) (string, bool) {
|
||
v, ok := m[key]
|
||
return v, ok
|
||
}
|
||
}
|
||
|
||
// fakeReadFile 依 map 回傳檔案內容;不存在時回 os.ErrNotExist 類錯誤。
|
||
func fakeReadFile(m map[string]string) ReadFileFunc {
|
||
return func(path string) ([]byte, error) {
|
||
if data, ok := m[path]; ok {
|
||
return []byte(data), nil
|
||
}
|
||
return nil, errors.New("file does not exist")
|
||
}
|
||
}
|
||
|
||
const sampleConfig = `logins:
|
||
- name: other.example.com
|
||
url: https://other.example.com
|
||
token: other-token
|
||
default: false
|
||
- name: gitea.alterminal.com
|
||
url: https://gitea.alterminal.com
|
||
token: site-token
|
||
default: true
|
||
preferences:
|
||
editor: false
|
||
`
|
||
|
||
func TestResolveTokenOrdering(t *testing.T) {
|
||
base := AuthOptions{
|
||
SiteURL: "https://gitea.alterminal.com",
|
||
Env: fakeEnv(nil),
|
||
ReadFile: fakeReadFile(map[string]string{
|
||
"/cfg/config.yml": sampleConfig,
|
||
}),
|
||
ConfigPath: "/cfg/config.yml",
|
||
}
|
||
|
||
t.Run("flag wins over env and config", func(t *testing.T) {
|
||
opts := base
|
||
opts.FlagToken = "flag-token"
|
||
opts.Env = fakeEnv(map[string]string{"TEAI_TOKEN": "env-token"})
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "flag-token" || src != SourceFlag {
|
||
t.Fatalf("want flag-token/flag, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("env wins over config", func(t *testing.T) {
|
||
opts := base
|
||
opts.Env = fakeEnv(map[string]string{"TEAI_TOKEN": "env-token"})
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "env-token" || src != SourceEnv {
|
||
t.Fatalf("want env-token/env, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("config used when flag and env absent", func(t *testing.T) {
|
||
opts := base
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "site-token" || src != SourceConfig {
|
||
t.Fatalf("want site-token/config, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("none when all sources absent", func(t *testing.T) {
|
||
opts := base
|
||
opts.ConfigPath = "/cfg/missing.yml"
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "" || src != SourceNone {
|
||
t.Fatalf("want \"\"/none, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("blank env var is skipped", func(t *testing.T) {
|
||
opts := base
|
||
opts.Env = fakeEnv(map[string]string{"TEAI_TOKEN": " "})
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "site-token" || src != SourceConfig {
|
||
t.Fatalf("want site-token/config, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("blank flag is skipped", func(t *testing.T) {
|
||
opts := base
|
||
opts.FlagToken = " "
|
||
opts.Env = fakeEnv(map[string]string{"TEAI_TOKEN": "env-token"})
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "env-token" || src != SourceEnv {
|
||
t.Fatalf("want env-token/env, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("TEAI_CONFIG points config path", func(t *testing.T) {
|
||
opts := base
|
||
opts.ConfigPath = "" // 不用 --config,改用 TEAI_CONFIG
|
||
opts.Env = fakeEnv(map[string]string{
|
||
"TEAI_CONFIG": "/cfg/config.yml",
|
||
})
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "site-token" || src != SourceConfig {
|
||
t.Fatalf("want site-token/config via TEAI_CONFIG, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("explicit --config beats TEAI_CONFIG", func(t *testing.T) {
|
||
opts := base // ConfigPath = /cfg/config.yml(有 site-token)
|
||
opts.Env = fakeEnv(map[string]string{
|
||
"TEAI_CONFIG": "/cfg/other.yml",
|
||
})
|
||
read := fakeReadFile(map[string]string{
|
||
"/cfg/config.yml": sampleConfig,
|
||
"/cfg/other.yml": "logins:\n - name: x\n url: https://gitea.alterminal.com\n token: other\n default: true\n",
|
||
})
|
||
opts.ReadFile = read
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "site-token" || src != SourceConfig {
|
||
t.Fatalf("want site-token from explicit config, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
|
||
t.Run("TEA_CONFIG is ignored", func(t *testing.T) {
|
||
opts := AuthOptions{
|
||
SiteURL: "https://gitea.alterminal.com",
|
||
Env: fakeEnv(map[string]string{
|
||
"TEA_CONFIG": "/cfg/config.yml",
|
||
}),
|
||
ReadFile: fakeReadFile(map[string]string{
|
||
"/cfg/config.yml": sampleConfig,
|
||
"/home/.config/teai/config.yml": "logins:\n - name: x\n url: https://gitea.alterminal.com\n token: teai-token\n default: true\n",
|
||
}),
|
||
Home: "/home",
|
||
}
|
||
tok, src, err := ResolveToken(opts)
|
||
if err != nil || tok != "teai-token" || src != SourceConfig {
|
||
t.Fatalf("want teai-token from teai config, got %q/%v (err %v)", tok, src, err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func TestParseConfigPicksMatchingLogin(t *testing.T) {
|
||
t.Run("host match preferred over default", func(t *testing.T) {
|
||
cfg := `logins:
|
||
- name: other
|
||
url: https://other.example.com
|
||
token: wrong-token
|
||
default: true
|
||
- name: target
|
||
url: https://gitea.alterminal.com
|
||
token: right-token
|
||
default: false
|
||
`
|
||
login, err := parseConfig([]byte(cfg), "https://gitea.alterminal.com")
|
||
if err != nil {
|
||
t.Fatalf("parse error: %v", err)
|
||
}
|
||
if login.Token != "right-token" {
|
||
t.Fatalf("host-matched login should win, got token %q", login.Token)
|
||
}
|
||
})
|
||
|
||
t.Run("quoted token unquoted", func(t *testing.T) {
|
||
cfg := "logins:\n - name: t\n url: https://gitea.alterminal.com\n token: \"quoted-token\"\n default: true\n"
|
||
login, err := parseConfig([]byte(cfg), "https://gitea.alterminal.com")
|
||
if err != nil {
|
||
t.Fatalf("parse error: %v", err)
|
||
}
|
||
if login.Token != "quoted-token" {
|
||
t.Fatalf("want quoted-token (unquoted), got %q", login.Token)
|
||
}
|
||
})
|
||
|
||
t.Run("malformed login entry errors", func(t *testing.T) {
|
||
cfg := "logins:\n - name: t\n url: https://gitea.alterminal.com\n oops\n"
|
||
if _, err := parseConfig([]byte(cfg), ""); err == nil {
|
||
t.Fatal("malformed line should error")
|
||
}
|
||
})
|
||
|
||
t.Run("no logins yields empty", func(t *testing.T) {
|
||
cfg := "preferences:\n editor: false\n"
|
||
login, err := parseConfig([]byte(cfg), "")
|
||
if err != nil || login.Token != "" {
|
||
t.Fatalf("want empty login, got %+v (err %v)", login, err)
|
||
}
|
||
})
|
||
}
|