Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
187 lines
5.4 KiB
Go
187 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 auth
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// agentCodeSignalEnvs is every env DetectAgentCode consults. Tests clear them
|
|
// all so each case starts clean (the suite itself runs under a real host).
|
|
var agentCodeSignalEnvs = []string{
|
|
AgentCodeEnv,
|
|
"CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT",
|
|
"OPENCLAW_BUNDLE_ROOT", "OPENCLAW_RUNTIME_ROLE",
|
|
"HERMES_HOME", "CODEX_SANDBOX",
|
|
"VSCODE_BRAND", "__CFBundleIdentifier",
|
|
"TERM_PROGRAM", "DWS_CHANNEL",
|
|
}
|
|
|
|
func clearAgentCodeEnv(t *testing.T) {
|
|
t.Helper()
|
|
for _, k := range agentCodeSignalEnvs {
|
|
t.Setenv(k, "")
|
|
}
|
|
}
|
|
|
|
func TestDetectAgentCode_HostDeclaration_T0(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv(AgentCodeEnv, "QoderWork")
|
|
code, sig := DetectAgentCode()
|
|
if code != "QoderWork" {
|
|
t.Fatalf("want verbatim QoderWork, got %q", code)
|
|
}
|
|
if !strings.HasPrefix(sig, "env:"+AgentCodeEnv) {
|
|
t.Fatalf("want env signal, got %q", sig)
|
|
}
|
|
}
|
|
|
|
func TestDetectAgentCode_VerifiedSignatures_T1(t *testing.T) {
|
|
cases := []struct {
|
|
env, val, want string
|
|
}{
|
|
{"CLAUDECODE", "1", "claudecode"},
|
|
{"CLAUDE_CODE_ENTRYPOINT", "cli", "claudecode"},
|
|
{"OPENCLAW_BUNDLE_ROOT", "/Users/x/.openclaw-bundle", "openclaw"},
|
|
{"HERMES_HOME", "/Users/x/.hermes", "hermes"},
|
|
{"CODEX_SANDBOX", "seatbelt", "codex"},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.env, func(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv(c.env, c.val)
|
|
code, sig := DetectAgentCode()
|
|
if code != c.want {
|
|
t.Fatalf("%s=%s: want %q, got %q", c.env, c.val, c.want, code)
|
|
}
|
|
if !strings.HasPrefix(sig, "sig:") {
|
|
t.Fatalf("want sig:* signal, got %q", sig)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDetectAgentCode_VSCodeBrand_T2(t *testing.T) {
|
|
cases := map[string]string{
|
|
"Qoder": "qoder",
|
|
"Cursor": "cursor",
|
|
"Visual Studio Code": "vscode",
|
|
"Windsurf": "windsurf",
|
|
"Trae": "trae",
|
|
"SomeNewFork": "somenewfork", // generic coverage of future forks
|
|
}
|
|
for brand, want := range cases {
|
|
t.Run(brand, func(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv("VSCODE_BRAND", brand)
|
|
code, sig := DetectAgentCode()
|
|
if code != want {
|
|
t.Fatalf("VSCODE_BRAND=%q: want %q, got %q", brand, want, code)
|
|
}
|
|
if sig != "env:VSCODE_BRAND" {
|
|
t.Fatalf("want env:VSCODE_BRAND signal, got %q", sig)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDetectAgentCode_BundleID_T3(t *testing.T) {
|
|
cases := map[string]string{
|
|
"com.qoder.ide": "qoder",
|
|
"com.todesktop.230313mzl4w4u92": "cursor",
|
|
"com.microsoft.VSCode": "vscode",
|
|
"com.workbuddy.workbuddy": "workbuddy",
|
|
}
|
|
for id, want := range cases {
|
|
t.Run(id, func(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv("__CFBundleIdentifier", id)
|
|
code, sig := DetectAgentCode()
|
|
if code != want {
|
|
t.Fatalf("bundle %q: want %q, got %q", id, want, code)
|
|
}
|
|
if !strings.HasPrefix(sig, "bundle:") {
|
|
t.Fatalf("want bundle:* signal, got %q", sig)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// An unknown bundle id (e.g. a plain terminal) must NOT be labeled.
|
|
func TestDetectAgentCode_UnknownBundleIsEmpty(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv("__CFBundleIdentifier", "com.googlecode.iterm2")
|
|
code, _ := DetectAgentCode()
|
|
if code != "" {
|
|
t.Fatalf("unknown bundle must be empty, got %q", code)
|
|
}
|
|
}
|
|
|
|
func TestDetectAgentCode_FallbackEmpty(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
code, sig := DetectAgentCode()
|
|
if code != "" {
|
|
t.Fatalf("want empty code, got %q", code)
|
|
}
|
|
if sig != "" {
|
|
t.Fatalf("want empty signal, got %q", sig)
|
|
}
|
|
}
|
|
|
|
// TERM_PROGRAM and DWS_CHANNEL must never decide agent_code.
|
|
func TestDetectAgentCode_IgnoresNoise(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv("TERM_PROGRAM", "iTerm.app")
|
|
t.Setenv("DWS_CHANNEL", "Qoderwork")
|
|
code, _ := DetectAgentCode()
|
|
if code != "" {
|
|
t.Fatalf("noise must not decide agent_code; want empty, got %q", code)
|
|
}
|
|
}
|
|
|
|
// Precedence: explicit declaration (T0) > env signature (T1) > VSCODE_BRAND
|
|
// (T2). A CLI agent running inside an IDE reports the CLI agent.
|
|
func TestDetectAgentCode_Precedence(t *testing.T) {
|
|
clearAgentCodeEnv(t)
|
|
t.Setenv("CLAUDECODE", "1") // T1
|
|
t.Setenv("VSCODE_BRAND", "Qoder") // T2
|
|
if code, _ := DetectAgentCode(); code != "claudecode" {
|
|
t.Fatalf("T1 must beat T2, got %q", code)
|
|
}
|
|
t.Setenv(AgentCodeEnv, "workbuddy") // T0
|
|
if code, _ := DetectAgentCode(); code != "workbuddy" {
|
|
t.Fatalf("T0 must beat all, got %q", code)
|
|
}
|
|
}
|
|
|
|
func TestNormalizeAgentCode(t *testing.T) {
|
|
cases := map[string]string{
|
|
"claude": "claudecode",
|
|
"Claude-Code": "claudecode",
|
|
"CLAUDECODE": "claudecode",
|
|
"Qoderwork": "QoderWork",
|
|
"WorkBuddy": "workbuddy",
|
|
"Visual Studio Code": "vscode",
|
|
"Cursor": "cursor",
|
|
"": "",
|
|
"some-new-ide": "some-new-ide",
|
|
}
|
|
for in, want := range cases {
|
|
if got := normalizeAgentCode(in); got != want {
|
|
t.Errorf("normalizeAgentCode(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|