Files
wehub-resource-sync bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

161 lines
4.9 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package credential_test
import (
"context"
"testing"
extcred "github.com/larksuite/cli/extension/credential"
envprovider "github.com/larksuite/cli/extension/credential/env"
"github.com/larksuite/cli/internal/core"
"github.com/larksuite/cli/internal/credential"
"github.com/larksuite/cli/internal/envvars"
"github.com/larksuite/cli/internal/i18n"
"github.com/larksuite/cli/internal/keychain"
)
type noopKC struct{}
func (n *noopKC) Get(service, account string) (string, error) { return "", nil }
func (n *noopKC) Set(service, account, value string) error { return nil }
func (n *noopKC) Remove(service, account string) error { return nil }
func TestFullChain_EnvWins(t *testing.T) {
t.Setenv(envvars.CliAppID, "env_app")
t.Setenv(envvars.CliAppSecret, "env_secret")
t.Setenv(envvars.CliUserAccessToken, "env_uat")
ep := &envprovider.Provider{}
cp := credential.NewCredentialProvider(
[]extcred.Provider{ep},
nil, nil, nil,
)
acct, err := cp.ResolveAccount(context.Background())
if err != nil {
t.Fatal(err)
}
if acct.AppID != "env_app" {
t.Errorf("expected env_app, got %s", acct.AppID)
}
result, err := cp.ResolveToken(context.Background(), credential.TokenSpec{
Type: credential.TokenTypeUAT, AppID: "env_app",
})
if err != nil {
t.Fatal(err)
}
if result.Token != "env_uat" {
t.Errorf("expected env_uat, got %s", result.Token)
}
}
func TestFullChain_Fallthrough(t *testing.T) {
// env provider returns nil (no env vars set), falls through to default token
ep := &envprovider.Provider{}
mock := &mockDefaultTokenProvider{token: "mock_tok", scopes: "drive:read"}
cp := credential.NewCredentialProvider(
[]extcred.Provider{ep},
nil, mock, nil,
)
result, err := cp.ResolveToken(context.Background(), credential.TokenSpec{
Type: credential.TokenTypeUAT, AppID: "app1",
})
if err != nil {
t.Fatal(err)
}
if result.Token != "mock_tok" || result.Scopes != "drive:read" {
t.Errorf("unexpected: %+v", result)
}
}
type mockDefaultTokenProvider struct {
token string
scopes string
}
func (m *mockDefaultTokenProvider) ResolveToken(ctx context.Context, req credential.TokenSpec) (*credential.TokenResult, error) {
return &credential.TokenResult{Token: m.token, Scopes: m.scopes}, nil
}
func TestFullChain_ConfigStrictMode(t *testing.T) {
t.Setenv(envvars.CliAppID, "")
t.Setenv(envvars.CliAppSecret, "")
dir := t.TempDir()
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", dir)
botMode := core.StrictModeBot
multi := &core.MultiAppConfig{
Apps: []core.AppConfig{{
AppId: "cfg_app",
AppSecret: core.PlainSecret("cfg_secret"),
Brand: core.BrandLark,
StrictMode: &botMode,
}},
}
if err := core.SaveMultiAppConfig(multi); err != nil {
t.Fatal(err)
}
ep := &envprovider.Provider{}
defaultAcct := credential.NewDefaultAccountProvider(func() keychain.KeychainAccess { return &noopKC{} }, "")
cp := credential.NewCredentialProvider(
[]extcred.Provider{ep},
defaultAcct, nil, nil,
)
acct, err := cp.ResolveAccount(context.Background())
if err != nil {
t.Fatal(err)
}
if acct.SupportedIdentities != uint8(extcred.SupportsBot) {
t.Errorf("expected SupportsBot (%d), got %d", extcred.SupportsBot, acct.SupportedIdentities)
}
}
// TestFullChain_LangSurvivesProductionPath exercises the exact data flow the
// production Factory uses (factory_default.go Phase 3): disk → multi config →
// DefaultAccountProvider.ResolveAccount → Account → ToCliConfig. If Lang gets
// dropped at the credential boundary (as it would when Account lacks the field),
// shortcuts/common/runner.go RuntimeContext.Lang() returns "" and downstream
// consumers (mail signature, etc.) silently fall back to defaults — defeating
// the whole point of persisting --lang.
func TestFullChain_LangSurvivesProductionPath(t *testing.T) {
t.Setenv(envvars.CliAppID, "")
t.Setenv(envvars.CliAppSecret, "")
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
multi := &core.MultiAppConfig{
Apps: []core.AppConfig{{
AppId: "cfg_app",
AppSecret: core.PlainSecret("cfg_secret"),
Brand: core.BrandFeishu,
Lang: i18n.LangJaJP,
}},
}
if err := core.SaveMultiAppConfig(multi); err != nil {
t.Fatalf("SaveMultiAppConfig: %v", err)
}
defaultAcct := credential.NewDefaultAccountProvider(func() keychain.KeychainAccess { return &noopKC{} }, "")
acct, err := defaultAcct.ResolveAccount(context.Background())
if err != nil {
t.Fatalf("ResolveAccount: %v", err)
}
if acct.Lang != i18n.LangJaJP {
t.Errorf("Account.Lang = %q, want %q (DefaultAccountProvider must propagate Lang from config)", acct.Lang, i18n.LangJaJP)
}
cfg := acct.ToCliConfig()
if cfg == nil {
t.Fatal("ToCliConfig() = nil")
}
if cfg.Lang != i18n.LangJaJP {
t.Errorf("CliConfig.Lang = %q, want %q (this is the value RuntimeContext.Lang() reads in production)", cfg.Lang, i18n.LangJaJP)
}
}