Files
AutoZero/cdp/profile_test.go
T
Sakurasan d5dac5a6fc feat: AutoZero 通用 Agent 框架(从 AutoGo 五阶段拆分完成)
llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、
builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、
编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。

产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt)
留在 AutoGo,宿主接入方式见 MIGRATION.md。
2026-10-26 00:54:46 +08:00

187 lines
6.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package cdp
import (
"os"
"path/filepath"
"strings"
"testing"
)
// writeFile 建一个带内容的文件(自动建父目录)。
func writeFile(t *testing.T, path, content string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatal(err)
}
}
// TestCheckProfileWarnings:三个最常见的坑都要被明确讲出来。
//
// 这三条正是"配了 profile 却用不上登录态"的真实原因:
// 1. 指到了 profile 本体(Default/ 里面)而不是 user-data-dir;
// 2. 指到了 Chrome 默认目录(Chrome 136+ 禁止在其上开远程调试,且日常 Chrome 占着);
// 3. 那份 profile 正被一个运行中的 Chrome 占用。
func TestCheckProfileWarnings(t *testing.T) {
t.Run("目录不存在", func(t *testing.T) {
warns := CheckProfile(filepath.Join(t.TempDir(), "nope"))
if len(warns) != 1 || !strings.Contains(warns[0], "不存在") {
t.Fatalf("应提示目录不存在,实际 %v", warns)
}
})
t.Run("指向 profile 本体", func(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "Cookies"), "x")
warns := CheckProfile(dir)
if !hasSubstr(warns, "父目录") {
t.Fatalf("应提示 --user-data-dir 要指向父目录,实际 %v", warns)
}
})
t.Run("指向 Chrome 默认目录", func(t *testing.T) {
cands := defaultUserDataDirCandidates()
if len(cands) == 0 {
t.Skip("当前平台没有已知的默认目录")
}
// 直接对候选路径做判断(纯字符串逻辑,不需要它真的存在)。
if !IsChromeDefaultDir(cands[0]) {
t.Fatalf("IsChromeDefaultDir(%q) 应为 true", cands[0])
}
if IsChromeDefaultDir(filepath.Join(t.TempDir(), "somecopy")) {
t.Error("非默认目录不该被判为默认目录")
}
})
t.Run("被运行中的 Chrome 占用", func(t *testing.T) {
dir := t.TempDir()
writeFile(t, filepath.Join(dir, "Local State"), "{}")
writeFile(t, filepath.Join(dir, "SingletonLock"), "")
warns := CheckProfile(dir)
if !hasSubstr(warns, "SingletonLock") {
t.Fatalf("应提示 profile 被占用,实际 %v", warns)
}
})
}
func hasSubstr(list []string, sub string) bool {
for _, s := range list {
if strings.Contains(s, sub) {
return true
}
}
return false
}
// TestCopyProfile:复制要**保留登录态与扩展、跳过缓存、不带单实例锁**。
func TestCopyProfile(t *testing.T) {
src := t.TempDir()
dst := filepath.Join(t.TempDir(), "copy")
// 要保留的:登录态与扩展。
writeFile(t, filepath.Join(src, "Local State"), "{}")
writeFile(t, filepath.Join(src, "Default", "Cookies"), "cookies-db")
writeFile(t, filepath.Join(src, "Default", "Preferences"), "{}")
writeFile(t, filepath.Join(src, "Default", "Extensions", "abc", "manifest.json"), "{}")
// 要跳过的:缓存(体积大且与登录态无关)。
writeFile(t, filepath.Join(src, "Default", "Cache", "big.bin"), strings.Repeat("x", 4096))
writeFile(t, filepath.Join(src, "Default", "Code Cache", "big.bin"), strings.Repeat("y", 4096))
writeFile(t, filepath.Join(src, "Default", "Service Worker", "s.bin"), "z")
n, err := CopyProfile(src, dst)
if err != nil {
t.Fatalf("复制失败: %v", err)
}
if n == 0 {
t.Error("复制字节数为 0")
}
for _, want := range []string{
filepath.Join("Default", "Cookies"),
filepath.Join("Default", "Preferences"),
filepath.Join("Default", "Extensions", "abc", "manifest.json"),
"Local State",
} {
if _, err := os.Stat(filepath.Join(dst, want)); err != nil {
t.Errorf("应保留 %s:%v", want, err)
}
}
for _, skip := range []string{
filepath.Join("Default", "Cache"),
filepath.Join("Default", "Code Cache"),
filepath.Join("Default", "Service Worker"),
} {
if _, err := os.Stat(filepath.Join(dst, skip)); err == nil {
t.Errorf("缓存目录应被跳过:%s", skip)
}
}
if !LooksLikeBrowserProfile(dst) {
t.Error("复制结果应当被识别为浏览器 profile")
}
}
// TestCopyProfileRefusesRunningChrome:源被占用(SingletonLock)时必须拒绝——
// 运行中复制的 profile 可能不一致,而且锁文件带过去会让 Chrome 犯迷糊。
func TestCopyProfileRefusesRunningChrome(t *testing.T) {
src := t.TempDir()
writeFile(t, filepath.Join(src, "Local State"), "{}")
writeFile(t, filepath.Join(src, "SingletonLock"), "")
if _, err := CopyProfile(src, filepath.Join(t.TempDir(), "copy")); err == nil {
t.Fatal("源被占用时应拒绝复制")
} else if !strings.Contains(err.Error(), "退出 Chrome") {
t.Errorf("错误应提示先退出 Chrome:%v", err)
}
}
// TestCopyProfileNoSingletonInDst:目标里不该出现单实例锁,否则 Chrome 会以为另有一个实例在跑。
func TestCopyProfileNoSingletonInDst(t *testing.T) {
src := t.TempDir()
writeFile(t, filepath.Join(src, "Local State"), "{}")
writeFile(t, filepath.Join(src, "Default", "Cookies"), "c")
dst := filepath.Join(t.TempDir(), "copy")
if _, err := CopyProfile(src, dst); err != nil {
t.Fatal(err)
}
for _, f := range []string{"SingletonLock", "SingletonSocket", "SingletonCookie"} {
if _, err := os.Lstat(filepath.Join(dst, f)); err == nil {
t.Errorf("目标不该有 %s", f)
}
}
}
// TestCopyProfileRejectsProfileBodyAsSource:源指向 profile 本体时拒绝并说清该指哪。
func TestCopyProfileRejectsProfileBodyAsSource(t *testing.T) {
src := t.TempDir()
writeFile(t, filepath.Join(src, "Cookies"), "x")
if _, err := CopyProfile(src, filepath.Join(t.TempDir(), "copy")); err == nil {
t.Fatal("源是 profile 本体时应拒绝")
}
}
// TestPersistentProfileDir:默认目录必须是**固定**路径。
//
// 旧的实现用 os.MkdirTemp——每次都给出一个新目录,于是每次进程启动都像一台从没
// 登录过的新机器(登录一次也留不住)。这里断言的关键性质就是"两次调用同一个路径"。
func TestPersistentProfileDir(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
dir, err := PersistentProfileDir()
if err != nil {
t.Fatal(err)
}
want := filepath.Join(home, ".autogo", "browser-profile")
if dir != want {
t.Errorf("默认 profile = %q, want %q", dir, want)
}
again, err := PersistentProfileDir()
if err != nil {
t.Fatal(err)
}
if again != dir {
t.Errorf("两次调用应返回同一路径(临时目录才会每次不同):%q vs %q", dir, again)
}
}