Files
dingtalk-workspace-cli/test/unit/pat_host_owned_signal_test.go
xuanandshangguanxuan.sgx 2a82d07311 fix(pat): support batch agentCode and guarded grants (#455)
* fix(pat): carry agentCode in batch auth args

* fix(pat): let core default missing agent code

* fix(pat): require yes for batch grants

* test(pat): cover cli authorization matrix

* fix(pat): keep canonical agent code env only

---------

Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
2026-06-11 19:21:25 +08:00

121 lines
3.5 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 unit_test
import (
"testing"
authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
)
// TestHostOwnsPATFlow_OnlySignal is the wire-level guard for the
// "custom authorization card" contract: the CLI switches to host-owned
// PAT mode iff the host injects DINGTALK_DWS_AGENTCODE. DINGTALK_AGENT /
// claw-type is purely a server-side routing tag and must NOT influence the
// decision, in either direction.
//
// Regression guard: several earlier drafts conflated the two signals,
// causing third-party Agent hosts that only set DINGTALK_DWS_AGENTCODE
// to still see a browser popup (CLI-owned fallback). This table locks
// in the decoupling.
func TestHostOwnsPATFlow_OnlySignal(t *testing.T) {
cases := []struct {
name string
agentCode string
reversed string
agentEnv string
want bool
}{
{
name: "no signal → CLI-owned",
agentCode: "",
agentEnv: "",
want: false,
},
{
name: "agent code only → host-owned",
agentCode: "agt-cursor",
agentEnv: "",
want: true,
},
{
name: "reversed draft agent code only → CLI-owned",
agentCode: "",
reversed: "agt-compat",
agentEnv: "",
want: false,
},
{
name: "agent code + DINGTALK_AGENT=default → host-owned",
agentCode: "agt-cursor",
agentEnv: "default",
want: true,
},
{
name: "agent code + DINGTALK_AGENT=business → host-owned",
agentCode: "agt-cursor",
agentEnv: "sales-copilot",
want: true,
},
{
name: "DINGTALK_AGENT=business, no agent code → CLI-owned",
agentCode: "",
agentEnv: "sales-copilot",
want: false,
},
{
name: "DINGTALK_AGENT=default, no agent code → CLI-owned",
agentCode: "",
agentEnv: "default",
want: false,
},
{
name: "whitespace-only agent code → CLI-owned",
agentCode: " ",
agentEnv: "sales-copilot",
want: false,
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Setenv(authpkg.AgentCodeEnv, tc.agentCode)
t.Setenv("DWS_DINGTALK_AGENTCODE", tc.reversed)
// DINGTALK_AGENT is set purely to demonstrate that it does NOT
// influence the host-owned decision. The literal env name is
// used here because the auth package no longer exports a
// DingTalkAgentEnv constant (it is not part of the PAT
// decision surface).
t.Setenv("DINGTALK_AGENT", tc.agentEnv)
if got := authpkg.HostOwnsPATFlow(); got != tc.want {
t.Fatalf(
"HostOwnsPATFlow() = %v, want %v (agentCode=%q, DINGTALK_AGENT=%q)",
got, tc.want, tc.agentCode, tc.agentEnv,
)
}
if tc.want {
gotCode, gotSource := authpkg.AgentCodeFromEnv()
wantCode := tc.agentCode
wantSource := authpkg.AgentCodeEnv
if gotCode != wantCode || gotSource != wantSource {
t.Fatalf("AgentCodeFromEnv() = (%q, %q), want (%q, %q)",
gotCode, gotSource, wantCode, wantSource)
}
}
})
}
}