From 16ed51252e46a442cdfd1428e8ec058e4a98ba6f Mon Sep 17 00:00:00 2001 From: luozhixiong Date: Wed, 15 Jul 2026 16:05:33 +0800 Subject: [PATCH] test(credential): pin the TAT mint-then-cache happy path First resolve mints over HTTP, second is served from the sync.Once cache with exactly one HTTP call total. --- internal/credential/default_provider_test.go | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/internal/credential/default_provider_test.go b/internal/credential/default_provider_test.go index 6c9da9d6e..b94dda15e 100644 --- a/internal/credential/default_provider_test.go +++ b/internal/credential/default_provider_test.go @@ -6,6 +6,7 @@ package credential import ( "context" "errors" + "io" "net/http" "strings" "testing" @@ -185,3 +186,47 @@ func TestDefaultTokenProvider_TATChecksAppBeforeAnyTokenWork(t *testing.T) { t.Fatal("token work started for a mismatched app: the check must run before any HTTP client is built") } } + +// countingTATTripper serves a canned successful TAT response and counts calls. +type countingTATTripper struct{ calls int } + +func (c *countingTATTripper) RoundTrip(*http.Request) (*http.Response, error) { + c.calls++ + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(`{"code":0,"access_token":"tat-token-value"}`)), + Header: http.Header{"Content-Type": []string{"application/json"}}, + }, nil +} + +// TAT happy path: the first request mints the token over HTTP, the second is +// served from the sync.Once cache without another HTTP call. +func TestDefaultTokenProvider_TATSuccessAndCacheHit(t *testing.T) { + t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) + multi := &core.MultiAppConfig{CurrentApp: "tenant_a", Apps: []core.AppConfig{{ + Name: "tenant_a", AppId: "cli_a", AppSecret: core.PlainSecret("your-secret"), Brand: core.BrandFeishu, + }}} + if err := core.SaveMultiAppConfig(multi); err != nil { + t.Fatalf("SaveMultiAppConfig: %v", err) + } + + tripper := &countingTATTripper{} + tp := NewDefaultTokenProvider( + NewDefaultAccountProvider(nil, "tenant_a"), + func() (*http.Client, error) { return &http.Client{Transport: tripper}, nil }, + nil, + ) + + req := TokenSpec{Type: TokenTypeTAT, AppID: "cli_a"} + first, err := tp.ResolveToken(context.Background(), req) + if err != nil || first.Token != "tat-token-value" { + t.Fatalf("first resolve = %+v, %v; want minted token", first, err) + } + second, err := tp.ResolveToken(context.Background(), req) + if err != nil || second.Token != "tat-token-value" { + t.Fatalf("second resolve = %+v, %v; want cached token", second, err) + } + if tripper.calls != 1 { + t.Fatalf("HTTP calls = %d, want exactly 1 (second resolve must hit the cache)", tripper.calls) + } +}