247 lines
8.4 KiB
Go
247 lines
8.4 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
|
|
)
|
|
|
|
func TestRuntimeRunnerAggregatesCommaSeparatedProfiles(t *testing.T) {
|
|
setupAuthLogoutProfiles(t,
|
|
authLogoutTestToken("corp_a"),
|
|
authLogoutTestToken("corp_b"),
|
|
)
|
|
authpkg.SetRuntimeProfile("corp_a, corp_b")
|
|
|
|
runner := &runtimeRunner{fallback: multiProfileFallbackRunner{}}
|
|
result, err := runner.Run(context.Background(), executor.Invocation{
|
|
Kind: "helper_invocation",
|
|
CanonicalProduct: "contact",
|
|
Tool: "get_current_user_profile",
|
|
Params: map[string]any{"limit": 10},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Run() error = %v", err)
|
|
}
|
|
if got := authpkg.RuntimeProfile(); got != "corp_a, corp_b" {
|
|
t.Fatalf("runtime profile after Run = %q, want restored raw selector", got)
|
|
}
|
|
|
|
content := result.Response["content"].(map[string]any)
|
|
if content["multiProfile"] != true {
|
|
t.Fatalf("multiProfile = %#v, want true", content["multiProfile"])
|
|
}
|
|
if content["success"] != true {
|
|
t.Fatalf("success = %#v, want true", content["success"])
|
|
}
|
|
profiles := content["profiles"].([]any)
|
|
if len(profiles) != 2 {
|
|
t.Fatalf("profiles len = %d, want 2", len(profiles))
|
|
}
|
|
for i, wantCorpID := range []string{"corp_a", "corp_b"} {
|
|
entry := profiles[i].(map[string]any)
|
|
if entry["corpId"] != wantCorpID {
|
|
t.Fatalf("profiles[%d].corpId = %#v, want %q", i, entry["corpId"], wantCorpID)
|
|
}
|
|
if entry["ok"] != true {
|
|
t.Fatalf("profiles[%d].ok = %#v, want true", i, entry["ok"])
|
|
}
|
|
resultPayload := entry["result"].(map[string]any)
|
|
wantProfile := wantCorpID + ":user-" + wantCorpID
|
|
if resultPayload["runtimeProfile"] != wantProfile {
|
|
t.Fatalf("profiles[%d].result.runtimeProfile = %#v, want %q", i, resultPayload["runtimeProfile"], wantProfile)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRuntimeRunnerDeduplicatesCommaSeparatedProfilesByCorpID(t *testing.T) {
|
|
configDir := setupAuthLogoutProfiles(t, authLogoutTestToken("corp_a"), authLogoutTestToken("corp_b"))
|
|
authpkg.SetRuntimeProfile("corp_a, corp_a org,corp_b")
|
|
|
|
selections, multi, err := resolveMultiProfileSelections(configDir, authpkg.RuntimeProfile())
|
|
if err != nil {
|
|
t.Fatalf("resolveMultiProfileSelections() error = %v", err)
|
|
}
|
|
if !multi {
|
|
t.Fatal("multi = false, want true")
|
|
}
|
|
if len(selections) != 2 {
|
|
t.Fatalf("selections len = %d, want 2", len(selections))
|
|
}
|
|
if selections[0].Profile.CorpID != "corp_a" || selections[1].Profile.CorpID != "corp_b" {
|
|
t.Fatalf("resolved corp IDs = %q, %q; want corp_a, corp_b", selections[0].Profile.CorpID, selections[1].Profile.CorpID)
|
|
}
|
|
}
|
|
|
|
func TestRuntimeRunnerDeduplicatesByResolvedIdentityInSameCorp(t *testing.T) {
|
|
first := authLogoutTestToken("corp_same")
|
|
first.UserID = "user_1"
|
|
second := authLogoutTestToken("corp_same")
|
|
second.AccessToken = "access-second"
|
|
second.RefreshToken = "refresh-second"
|
|
second.UserID = "user_2"
|
|
configDir := setupAuthLogoutProfiles(t, first, second)
|
|
|
|
selections, multi, err := resolveMultiProfileSelections(
|
|
configDir,
|
|
"corp_same,corp_same:user_1,corp_same:user_2",
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("resolveMultiProfileSelections() error = %v", err)
|
|
}
|
|
if !multi {
|
|
t.Fatal("multi = false, want true")
|
|
}
|
|
if len(selections) != 2 {
|
|
t.Fatalf("selections len = %d, want 2: %#v", len(selections), selections)
|
|
}
|
|
got := []string{
|
|
authpkg.ProfileSelector(selections[0].Profile),
|
|
authpkg.ProfileSelector(selections[1].Profile),
|
|
}
|
|
if strings.Join(got, ",") != "corp_same:user_2,corp_same:user_1" {
|
|
t.Fatalf("resolved identities = %v, want current user_2 then user_1", got)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageRuntimeRunnerDeduplicatesReservedAndOrganizationAliasesForBlankProfile(t *testing.T) {
|
|
exact := authLogoutTestToken("corp_blank_alias")
|
|
exact.UserID = "identity_exact_alias"
|
|
configDir := setupAuthLogoutProfiles(t, exact)
|
|
blank := authLogoutTestToken("corp_blank_alias")
|
|
blank.AccessToken = "access-unresolved-alias"
|
|
blank.RefreshToken = "refresh-unresolved-alias"
|
|
blank.UserID = ""
|
|
blank.UserName = ""
|
|
if err := authpkg.SaveTokenData(configDir, blank); err != nil {
|
|
t.Fatalf("SaveTokenData(blank) error = %v", err)
|
|
}
|
|
cfg, err := authpkg.LoadProfiles(configDir)
|
|
if err != nil {
|
|
t.Fatalf("LoadProfiles() error = %v", err)
|
|
}
|
|
var reserved string
|
|
for _, profile := range cfg.Profiles {
|
|
if profile.CorpID == blank.CorpID && profile.UserID == "" {
|
|
reserved = authpkg.ProfileSelectionSelector(profile, cfg)
|
|
break
|
|
}
|
|
}
|
|
if reserved == "" || reserved == blank.CorpID {
|
|
t.Fatalf("blank selector = %q, want reserved selector", reserved)
|
|
}
|
|
|
|
selections, multi, err := resolveMultiProfileSelections(configDir, reserved+","+blank.CorpID)
|
|
if err != nil {
|
|
t.Fatalf("resolveMultiProfileSelections() error = %v", err)
|
|
}
|
|
if !multi || len(selections) != 1 {
|
|
t.Fatalf("blank aliases = multi %v selections %#v, want one identity", multi, selections)
|
|
}
|
|
if selections[0].Selector != reserved || selections[0].Profile.UserID != "" {
|
|
t.Fatalf("blank selection = %#v, want first reserved alias preserved", selections[0])
|
|
}
|
|
}
|
|
|
|
func TestRuntimeRunnerKeepsSingleProfileBehavior(t *testing.T) {
|
|
setupAuthLogoutProfiles(t, authLogoutTestToken("corp_a"), authLogoutTestToken("corp_b"))
|
|
authpkg.SetRuntimeProfile("corp_a")
|
|
|
|
runner := &runtimeRunner{fallback: multiProfileFallbackRunner{}}
|
|
result, err := runner.Run(context.Background(), executor.Invocation{
|
|
Kind: "helper_invocation",
|
|
CanonicalProduct: "contact",
|
|
Tool: "get_current_user_profile",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Run() error = %v", err)
|
|
}
|
|
if _, ok := result.Response["content"].(map[string]any)["multiProfile"]; ok {
|
|
t.Fatalf("single profile unexpectedly returned aggregate content: %#v", result.Response)
|
|
}
|
|
if got := result.Response["content"].(map[string]any)["runtimeProfile"]; got != "corp_a:user-corp_a" {
|
|
t.Fatalf("fallback runtime profile = %#v, want exact identity selector", got)
|
|
}
|
|
if got := authpkg.RuntimeProfile(); got != "corp_a" {
|
|
t.Fatalf("runtime profile after Run = %q, want corp_a", got)
|
|
}
|
|
}
|
|
|
|
func TestRuntimeRunnerRejectsAmbiguousSingleProfile(t *testing.T) {
|
|
first := authLogoutTestToken("corp_first")
|
|
first.CorpName = "Shared Org"
|
|
second := authLogoutTestToken("corp_second")
|
|
second.CorpName = "Shared Org"
|
|
setupAuthLogoutProfiles(t, first, second)
|
|
authpkg.SetRuntimeProfile("Shared Org")
|
|
|
|
runner := &runtimeRunner{fallback: multiProfileFallbackRunner{}}
|
|
_, err := runner.Run(context.Background(), executor.Invocation{
|
|
Kind: "helper_invocation",
|
|
CanonicalProduct: "contact",
|
|
Tool: "get_current_user_profile",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("Run() accepted ambiguous single profile selector")
|
|
}
|
|
for _, candidate := range []string{"corp_first", "corp_second"} {
|
|
if !strings.Contains(err.Error(), candidate) {
|
|
t.Fatalf("error = %q, want candidate %q", err.Error(), candidate)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCommaNamedProfileStillResolvesAsSingleProfile(t *testing.T) {
|
|
configDir := setupAuthLogoutProfiles(t, authLogoutTestToken("corp_comma"), authLogoutTestToken("corp_other"))
|
|
cfg, err := authpkg.LoadProfiles(configDir)
|
|
if err != nil {
|
|
t.Fatalf("LoadProfiles() error = %v", err)
|
|
}
|
|
for i := range cfg.Profiles {
|
|
if cfg.Profiles[i].CorpID == "corp_comma" {
|
|
cfg.Profiles[i].Name = "alpha,beta"
|
|
}
|
|
}
|
|
if err := authpkg.SaveProfiles(configDir, cfg); err != nil {
|
|
t.Fatalf("SaveProfiles() error = %v", err)
|
|
}
|
|
|
|
selections, multi, err := resolveMultiProfileSelections(configDir, "alpha,beta")
|
|
if err != nil {
|
|
t.Fatalf("resolveMultiProfileSelections() error = %v", err)
|
|
}
|
|
if multi {
|
|
t.Fatalf("multi = true, want false; selections=%#v", selections)
|
|
}
|
|
}
|
|
|
|
func TestCommaSeparatedProfileRejectsEmptySelector(t *testing.T) {
|
|
configDir := setupAuthLogoutProfiles(t, authLogoutTestToken("corp_a"), authLogoutTestToken("corp_b"))
|
|
|
|
_, _, err := resolveMultiProfileSelections(configDir, "corp_a,,corp_b")
|
|
if err == nil {
|
|
t.Fatal("resolveMultiProfileSelections() error = nil, want validation error")
|
|
}
|
|
if !strings.Contains(err.Error(), "empty profile selector") {
|
|
t.Fatalf("error = %q, want empty profile selector", err.Error())
|
|
}
|
|
}
|
|
|
|
type multiProfileFallbackRunner struct{}
|
|
|
|
func (multiProfileFallbackRunner) Run(_ context.Context, invocation executor.Invocation) (executor.Result, error) {
|
|
invocation.Implemented = true
|
|
return executor.Result{
|
|
Invocation: invocation,
|
|
Response: map[string]any{
|
|
"content": map[string]any{
|
|
"runtimeProfile": authpkg.RuntimeProfile(),
|
|
"tool": invocation.Tool,
|
|
},
|
|
},
|
|
}, nil
|
|
}
|