// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package app import ( "bytes" "errors" "net/http" "path/filepath" "testing" "time" authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/keychain" ) func TestAuthStatusRefreshFailureLeavesStoredTokenIntact(t *testing.T) { // Isolate keychain storage to a per-test directory so the saved // token can't leak into other test packages running in parallel. t.Setenv(keychain.StorageDirEnv, t.TempDir()) t.Cleanup(func() { _ = keychain.Remove(keychain.Service, keychain.AccountToken) }) root := t.TempDir() configDir := filepath.Join(root, "config") t.Setenv("DWS_CONFIG_DIR", configDir) err := authpkg.SaveTokenData(configDir, &authpkg.TokenData{ AccessToken: "expired-access", RefreshToken: "refresh-123", ExpiresAt: time.Now().Add(-time.Hour), RefreshExpAt: time.Now().Add(24 * time.Hour), CorpID: "dingcorp", }) if err != nil { t.Skipf("SaveTokenData() unavailable in this environment: %v", err) } originalTransport := http.DefaultTransport t.Cleanup(func() { http.DefaultTransport = originalTransport }) http.DefaultTransport = roundTripFunc(func(req *http.Request) (*http.Response, error) { return nil, errors.New("refresh failed") }) cmd := NewRootCommand() var out bytes.Buffer cmd.SetOut(&out) cmd.SetErr(&out) cmd.SetArgs([]string{"auth", "status"}) if err := cmd.Execute(); err != nil { t.Fatalf("Execute() error = %v\noutput:\n%s", err, out.String()) } // Verify token data still exists in keychain after refresh failure if !authpkg.TokenDataExistsKeychain() { t.Fatal("secure token data should remain in keychain after refresh failure") } if !bytes.Contains(out.Bytes(), []byte("\"authenticated\"")) { t.Fatalf("output should still report authenticated status:\n%s", out.String()) } } type roundTripFunc func(*http.Request) (*http.Response, error) func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req) }