// 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) } }) }