package artifactfetch import ( "context" "os/exec" "path/filepath" "strings" "testing" "os" ) // 回归(2026-10-10 端到端验收实测):取件子进程用的是白名单环境(为防凭据外泄), // dws 取件器的运行变量必须显式透传。漏传时 dws 找不到配置目录与文件型 keychain, // 报"未登录"→ 被映射成 credentials 终态 → 钉钉取件在真实链路上整体失败 // (脚本单跑却正常,因为那只绕过了这个白名单)。 func TestChildEnvForwardsDWSVariables(t *testing.T) { t.Setenv("DWS_HOME", "/data/home") t.Setenv("DWS_CONFIG_DIR", "/data/dws") t.Setenv("DWS_DISABLE_KEYCHAIN", "1") t.Setenv("ARTIFACT_DINGTALK_FETCHER", "auto") t.Setenv("DWS_BIN", "/usr/local/bin/dws") t.Setenv("PGPASSWORD", "should-not-leak") runner, err := NewCommandRunner("/usr/bin/python3", "/app/scripts/artifact_fetch.py", "/spool", "", 1024) if err != nil { t.Fatal(err) } joined := strings.Join(runner.childEnv(), "\n") for _, want := range []string{ "DWS_HOME=/data/home", "DWS_CONFIG_DIR=/data/dws", "DWS_DISABLE_KEYCHAIN=1", "ARTIFACT_DINGTALK_FETCHER=auto", "DWS_BIN=/usr/local/bin/dws", "INGESTION_SPOOL_ROOT=/spool", "TZ=UTC", } { if !strings.Contains(joined, want) { t.Errorf("childEnv 缺少 %q", want) } } if strings.Contains(joined, "PGPASSWORD") { t.Fatal("白名单之外的变量不得进入子进程环境") } } // 未设置时不透传空值(让 dws 用自己的默认值)。 func TestChildEnvOmitsUnsetDWSVariables(t *testing.T) { for _, name := range dwsEnvNames { t.Setenv(name, "") } runner, err := NewCommandRunner("/usr/bin/python3", "/app/scripts/artifact_fetch.py", "/spool", "", 1024) if err != nil { t.Fatal(err) } if joined := strings.Join(runner.childEnv(), "\n"); strings.Contains(joined, "DWS_HOME=") { t.Fatalf("空值不应透传:%s", joined) } } // 失败时把脚本 stderr 尾部脱敏后带进错误,否则操作者只能看到裸错误码(本次排查就卡在这)。 func TestRunSurfacesSanitizedStderrOnFailure(t *testing.T) { python, err := exec.LookPath("python3") if err != nil { t.Skip("python3 not available") } dir := t.TempDir() script := filepath.Join(dir, "fake_fetch.py") payload := "import json,sys\n" + "sys.stderr.write('dws: 未登录,请先执行 dws auth login\\n')\n" + "print(json.dumps({'ok': False, 'code': 'credentials', 'error': 'not authenticated', 'node_id': 'sn-x'}))\n" if err := os.WriteFile(script, []byte(payload), 0o644); err != nil { t.Fatal(err) } runner, err := NewCommandRunner(python, script, dir, "", 4096) if err != nil { t.Fatal(err) } result, err := runner.Run(context.Background(), Manifest{Request: Request{NodeID: "sn-x"}}) if err != nil { t.Fatal(err) } if result.OK || result.Code != "credentials" { t.Fatalf("应返回失败结果:%+v", result) } if !strings.Contains(result.StderrTail, "dws") { t.Fatalf("应带回 stderr 尾部,实际 %q", result.StderrTail) } if detail := stderrDetail(result.StderrTail); !strings.Contains(detail, "未登录") { t.Fatalf("stderrDetail 应含原因,实际 %q", detail) } if suppressed := stderrDetail("Authorization: Bearer abc https://example/x"); strings.Contains(suppressed, "Bearer") { t.Fatalf("敏感内容必须被抑制,实际 %q", suppressed) } }