主覆盖门禁在 Linux CI 以 -0.0008pp 惜败 overall 非回归(changed-code 已 100%)。补 25 句纯函数分支覆盖抬高 overall: tokenResolutionError 四分支、profileSwitchProfileCells sameCorp 消歧、 authRefreshFailureError.Unwrap / withAuthRetrying / managesRuntimeOAuth、 ForceRefreshAccessToken 与 forceRefreshRejectedAccessToken 守卫、 getCachedRuntimeToken prefetch 缝。
136 lines
5.4 KiB
Go
136 lines
5.4 KiB
Go
// 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 (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/testseam"
|
|
)
|
|
|
|
func TestCrossPlatformCoverageTokenResolutionErrorBranches(t *testing.T) {
|
|
if err := tokenResolutionError(nil); err != nil {
|
|
t.Fatalf("nil error must pass through: %v", err)
|
|
}
|
|
if err := tokenResolutionError(context.Canceled); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("context cancellation must pass through unwrapped: %v", err)
|
|
}
|
|
if err := tokenResolutionError(context.DeadlineExceeded); !errors.Is(err, context.DeadlineExceeded) {
|
|
t.Fatalf("deadline exceeded must pass through unwrapped: %v", err)
|
|
}
|
|
if err := tokenResolutionError(authpkg.ErrTokenDataNotFound); err == nil ||
|
|
!strings.Contains(err.Error(), "dws auth login") {
|
|
t.Fatalf("not-found must become a login guidance error: %v", err)
|
|
}
|
|
generic := errors.New("keychain locked")
|
|
if err := tokenResolutionError(generic); err == nil ||
|
|
!strings.Contains(err.Error(), "resolve access token") || !errors.Is(err, generic) {
|
|
t.Fatalf("generic error must wrap with resolve access token: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageProfileSwitchCellsSameCorp(t *testing.T) {
|
|
profile := authpkg.Profile{CorpID: "corp-1", CorpName: "钉钉", UserName: "Alice", UserID: "u1"}
|
|
cfg := &authpkg.ProfilesConfig{Profiles: []authpkg.Profile{
|
|
profile,
|
|
{CorpID: "corp-1", CorpName: "钉钉", UserName: "Bob", UserID: "u2"},
|
|
}}
|
|
org, _ := profileSwitchProfileCells(profile, cfg)
|
|
if !strings.Contains(org, " / Alice") {
|
|
t.Fatalf("same-corp label must disambiguate with user name: %q", org)
|
|
}
|
|
|
|
noName := authpkg.Profile{CorpID: "corp-1", CorpName: "钉钉", UserID: "u1"}
|
|
org, _ = profileSwitchProfileCells(noName, cfg)
|
|
if !strings.Contains(org, " / u1") {
|
|
t.Fatalf("blank user name must fall back to user id: %q", org)
|
|
}
|
|
|
|
blankUser := authpkg.Profile{CorpID: "corp-1", CorpName: "钉钉"}
|
|
org, _ = profileSwitchProfileCells(blankUser, cfg)
|
|
if strings.Contains(org, " / ") {
|
|
t.Fatalf("blank user name and id must not append a suffix: %q", org)
|
|
}
|
|
|
|
org, _ = profileSwitchProfileCells(profile, nil)
|
|
if strings.Contains(org, " / ") {
|
|
t.Fatalf("nil config must not append a suffix: %q", org)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageAuthRefreshRetryHelpers(t *testing.T) {
|
|
var nilErr *authRefreshFailureError
|
|
if got := nilErr.Unwrap(); got != nil {
|
|
t.Fatalf("nil receiver Unwrap = %v, want nil", got)
|
|
}
|
|
rejection := errors.New("rejected")
|
|
refresh := errors.New("refresh boom")
|
|
wrapped := (&authRefreshFailureError{rejection: rejection, refresh: refresh}).Unwrap()
|
|
if len(wrapped) != 2 || !errors.Is(wrapped[0], rejection) || !errors.Is(wrapped[1], refresh) {
|
|
t.Fatalf("Unwrap must surface both causes: %v", wrapped)
|
|
}
|
|
|
|
if ctx := withAuthRetrying(nil); ctx == nil || !IsAuthRetrying(ctx) {
|
|
t.Fatal("withAuthRetrying(nil) must produce a retry-marked background context")
|
|
}
|
|
|
|
var nilRunner *runtimeRunner
|
|
if nilRunner.managesRuntimeOAuth(false) {
|
|
t.Fatal("nil runner must not manage runtime OAuth")
|
|
}
|
|
if (&runtimeRunner{}).managesRuntimeOAuth(true) {
|
|
t.Fatal("plugin-owned auth must opt out of runtime OAuth management")
|
|
}
|
|
if !(&runtimeRunner{}).managesRuntimeOAuth(false) {
|
|
t.Fatal("nil global flags must manage runtime OAuth")
|
|
}
|
|
if !(&runtimeRunner{globalFlags: &GlobalFlags{}}).managesRuntimeOAuth(false) {
|
|
t.Fatal("blank token must manage runtime OAuth")
|
|
}
|
|
if (&runtimeRunner{globalFlags: &GlobalFlags{Token: "tok"}}).managesRuntimeOAuth(false) {
|
|
t.Fatal("explicit token must not manage runtime OAuth")
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageForceRefreshRejectedGuards(t *testing.T) {
|
|
if _, err := ForceRefreshAccessToken(context.Background(), " "); err == nil ||
|
|
!strings.Contains(err.Error(), "config directory is empty") {
|
|
t.Fatalf("blank config dir error = %v", err)
|
|
}
|
|
testseam.Swap(t, &loadRefreshTokenData, func(string) (*authpkg.TokenData, error) { return nil, nil })
|
|
if _, err := ForceRefreshAccessToken(context.Background(), t.TempDir()); err == nil ||
|
|
!strings.Contains(err.Error(), "stored access token is empty") {
|
|
t.Fatalf("nil token data must report stored access token is empty: %v", err)
|
|
}
|
|
if _, err := forceRefreshRejectedAccessToken(context.Background(), " ", "tok"); err == nil ||
|
|
!strings.Contains(err.Error(), "config directory is empty") {
|
|
t.Fatalf("blank config dir error = %v", err)
|
|
}
|
|
if _, err := forceRefreshRejectedAccessToken(context.Background(), t.TempDir(), " "); err == nil ||
|
|
!strings.Contains(err.Error(), "rejected access token is empty") {
|
|
t.Fatalf("blank rejected token error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageGetCachedRuntimeTokenSeam(t *testing.T) {
|
|
token, err := getCachedRuntimeToken(context.Background())
|
|
if err != nil && strings.TrimSpace(token) != "" {
|
|
t.Fatalf("failed token resolution must not return a token: %q / %v", token, err)
|
|
}
|
|
}
|