package profile import ( "os" "path/filepath" "testing" ) // ---------------------------------------------------------------- // 模板与构造 // ---------------------------------------------------------------- func TestNewFromTemplate_Minimal(t *testing.T) { p, err := NewFromTemplate("test-min", TemplateMinimal) if err != nil { t.Fatalf("创建 minimal 模板失败: %v", err) } if p.Name != "test-min" { t.Errorf("Name = %q, want %q", p.Name, "test-min") } if p.Version != CurrentVersion { t.Errorf("Version = %d, want %d", p.Version, CurrentVersion) } if len(p.Collectors.Enabled) != 4 { t.Errorf("Enabled 采集器数量 = %d, want 4", len(p.Collectors.Enabled)) } } func TestNewFromTemplate_Standard(t *testing.T) { p, err := NewFromTemplate("test-std", TemplateStandard) if err != nil { t.Fatalf("创建 standard 模板失败: %v", err) } if len(p.Collectors.Enabled) != 7 { t.Errorf("Enabled 采集器数量 = %d, want 7", len(p.Collectors.Enabled)) } // standard 应包含 vscode found := false for _, name := range p.Collectors.Enabled { if name == "vscode" { found = true break } } if !found { t.Error("standard 模板应包含 vscode 采集器") } } func TestNewFromTemplate_Full(t *testing.T) { p, err := NewFromTemplate("test-full", TemplateFull) if err != nil { t.Fatalf("创建 full 模板失败: %v", err) } // full 模板 enabled 列表为空(表示全部启用) if len(p.Collectors.Enabled) != 0 { t.Errorf("full 模板 Enabled 应为空, got %d", len(p.Collectors.Enabled)) } if len(p.Collectors.Disabled) != 0 { t.Errorf("full 模板 Disabled 应为空, got %d", len(p.Collectors.Disabled)) } } func TestNewFromTemplate_Invalid(t *testing.T) { _, err := NewFromTemplate("test", "invalid") if err == nil { t.Error("无效模板类型应返回错误") } } // ---------------------------------------------------------------- // 验证 // ---------------------------------------------------------------- func TestValidate_ValidProfile(t *testing.T) { p, _ := NewFromTemplate("valid-profile", TemplateStandard) if err := p.Validate(); err != nil { t.Errorf("合法 Profile 验证失败: %v", err) } } func TestValidate_EmptyName(t *testing.T) { p := &Profile{Name: "", Version: 1} if err := p.Validate(); err == nil { t.Error("空名称应验证失败") } } func TestValidate_InvalidNameChars(t *testing.T) { p := &Profile{Name: "has space", Version: 1} if err := p.Validate(); err == nil { t.Error("含空格的名称应验证失败") } } func TestValidate_TooLongName(t *testing.T) { longName := "" for i := 0; i < 65; i++ { longName += "a" } p := &Profile{Name: longName, Version: 1} if err := p.Validate(); err == nil { t.Error("超过 64 字符的名称应验证失败") } } func TestValidate_InvalidVersion(t *testing.T) { p := &Profile{Name: "test", Version: 0} if err := p.Validate(); err == nil { t.Error("版本 0 应验证失败") } p.Version = 999 if err := p.Validate(); err == nil { t.Error("超出当前版本应验证失败") } } func TestValidate_EnabledDisabledConflict(t *testing.T) { p := &Profile{ Name: "test", Version: 1, Collectors: CollectorConfig{ Enabled: []string{"go", "node"}, Disabled: []string{"node"}, // 冲突 }, } if err := p.Validate(); err == nil { t.Error("enabled 和 disabled 有交集应验证失败") } } // ---------------------------------------------------------------- // IsCollectorEnabled // ---------------------------------------------------------------- func TestIsCollectorEnabled_AllEnabled(t *testing.T) { p := &Profile{Collectors: CollectorConfig{}} if !p.IsCollectorEnabled("go") { t.Error("enabled 为空时应全部启用") } } func TestIsCollectorEnabled_ExplicitEnabled(t *testing.T) { p := &Profile{ Collectors: CollectorConfig{ Enabled: []string{"go", "node"}, }, } if !p.IsCollectorEnabled("go") { t.Error("go 应启用") } if p.IsCollectorEnabled("vscode") { t.Error("vscode 不在 enabled 列表中,应禁用") } } func TestIsCollectorEnabled_ExplicitDisabled(t *testing.T) { p := &Profile{ Collectors: CollectorConfig{ Disabled: []string{"ssh"}, }, } if p.IsCollectorEnabled("ssh") { t.Error("ssh 在 disabled 列表中,应禁用") } if !p.IsCollectorEnabled("go") { t.Error("go 不在 disabled 中,应启用") } } // ---------------------------------------------------------------- // GetSetting / GetSettingBool / GetSettingStrings // ---------------------------------------------------------------- func TestGetSetting(t *testing.T) { p, _ := NewFromTemplate("test", TemplateStandard) val, ok := p.GetSetting("go", "include_tools") if !ok { t.Fatal("go.include_tools 应存在") } if val != true { t.Errorf("go.include_tools = %v, want true", val) } _, ok = p.GetSetting("go", "nonexistent") if ok { t.Error("不存在的 key 应返回 false") } _, ok = p.GetSetting("nonexistent", "key") if ok { t.Error("不存在的采集器应返回 false") } } func TestGetSettingBool(t *testing.T) { p, _ := NewFromTemplate("test", TemplateStandard) if !p.GetSettingBool("go", "include_tools", false) { t.Error("go.include_tools 应为 true") } if p.GetSettingBool("go", "nonexistent", false) { t.Error("不存在的 key 应返回默认值 false") } if !p.GetSettingBool("go", "nonexistent", true) { t.Error("不存在的 key 应返回默认值 true") } } func TestGetSettingStrings(t *testing.T) { p, _ := NewFromTemplate("test", TemplateStandard) filters := p.GetSettingStrings("go", "tools_filter") if len(filters) == 0 { t.Fatal("go.tools_filter 应有内容") } found := false for _, f := range filters { if f == "gopls" { found = true } } if !found { t.Error("tools_filter 应包含 gopls") } result := p.GetSettingStrings("go", "nonexistent") if result != nil { t.Error("不存在的 key 应返回 nil") } } // ---------------------------------------------------------------- // CRUD 操作 // ---------------------------------------------------------------- func TestSaveAndLoad(t *testing.T) { dir := t.TempDir() p, _ := NewFromTemplate("round-trip", TemplateStandard) path := filepath.Join(dir, "round-trip.yaml") if err := Save(path, p); err != nil { t.Fatalf("Save 失败: %v", err) } loaded, err := Load(path) if err != nil { t.Fatalf("Load 失败: %v", err) } if loaded.Name != p.Name { t.Errorf("Name = %q, want %q", loaded.Name, p.Name) } if loaded.Description != p.Description { t.Errorf("Description 不匹配") } if len(loaded.Collectors.Enabled) != len(p.Collectors.Enabled) { t.Errorf("Enabled 数量不匹配: got %d, want %d", len(loaded.Collectors.Enabled), len(p.Collectors.Enabled)) } } func TestSaveByName_LoadByName(t *testing.T) { dir := t.TempDir() p, _ := NewFromTemplate("my-profile", TemplateMinimal) if err := SaveByName(dir, p); err != nil { t.Fatalf("SaveByName 失败: %v", err) } loaded, err := LoadByName(dir, "my-profile") if err != nil { t.Fatalf("LoadByName 失败: %v", err) } if loaded.Name != "my-profile" { t.Errorf("Name = %q, want %q", loaded.Name, "my-profile") } } func TestLoad_FileNotExist(t *testing.T) { _, err := Load("/nonexistent/path.yaml") if err == nil { t.Error("加载不存在的文件应报错") } } func TestSave_InvalidProfile(t *testing.T) { dir := t.TempDir() p := &Profile{Name: "", Version: 1} // 名称为空,验证不通过 path := filepath.Join(dir, "invalid.yaml") if err := Save(path, p); err == nil { t.Error("保存验证不通过的 Profile 应报错") } } func TestDelete(t *testing.T) { dir := t.TempDir() p, _ := NewFromTemplate("to-delete", TemplateMinimal) SaveByName(dir, p) if err := Delete(dir, "to-delete"); err != nil { t.Fatalf("Delete 失败: %v", err) } if Exists(dir, "to-delete") { t.Error("删除后文件应不存在") } } func TestDelete_NotExist(t *testing.T) { dir := t.TempDir() if err := Delete(dir, "nonexistent"); err == nil { t.Error("删除不存在的 Profile 应报错") } } func TestExists(t *testing.T) { dir := t.TempDir() if Exists(dir, "nope") { t.Error("不存在的 Profile 应返回 false") } p, _ := NewFromTemplate("exists", TemplateMinimal) SaveByName(dir, p) if !Exists(dir, "exists") { t.Error("已保存的 Profile 应返回 true") } } func TestList(t *testing.T) { dir := t.TempDir() // 空目录 metas, err := List(dir) if err != nil { t.Fatalf("List 空目录失败: %v", err) } if len(metas) != 0 { t.Errorf("空目录应返回 0 个 Profile, got %d", len(metas)) } // 添加两个 Profile p1, _ := NewFromTemplate("alpha", TemplateMinimal) p2, _ := NewFromTemplate("beta", TemplateStandard) SaveByName(dir, p1) SaveByName(dir, p2) metas, err = List(dir) if err != nil { t.Fatalf("List 失败: %v", err) } if len(metas) != 2 { t.Errorf("应有 2 个 Profile, got %d", len(metas)) } } func TestList_NonExistentDir(t *testing.T) { metas, err := List("/nonexistent/dir") if err != nil { t.Fatalf("不存在的目录应返回 nil 而非错误: %v", err) } if metas != nil { t.Error("不存在的目录应返回 nil") } } // ---------------------------------------------------------------- // 原子写入验证 // ---------------------------------------------------------------- func TestSave_AtomicWrite(t *testing.T) { dir := t.TempDir() p, _ := NewFromTemplate("atomic-test", TemplateMinimal) path := filepath.Join(dir, "atomic-test.yaml") if err := Save(path, p); err != nil { t.Fatalf("Save 失败: %v", err) } // 临时文件不应残留 tmpPath := path + ".tmp" if _, err := os.Stat(tmpPath); !os.IsNotExist(err) { t.Error("临时文件应在保存后被清理") } // 真实文件应存在 if _, err := os.Stat(path); err != nil { t.Errorf("目标文件应存在: %v", err) } } // ---------------------------------------------------------------- // 模板辅助 // ---------------------------------------------------------------- func TestIsValidTemplateName(t *testing.T) { if !IsValidTemplateName("minimal") { t.Error("minimal 应是合法模板名") } if !IsValidTemplateName("standard") { t.Error("standard 应是合法模板名") } if !IsValidTemplateName("full") { t.Error("full 应是合法模板名") } if IsValidTemplateName("unknown") { t.Error("unknown 不应是合法模板名") } } func TestTemplateDescription(t *testing.T) { for _, tmpl := range ValidTemplateNames() { desc := TemplateDescription(tmpl) if desc == "" || desc == "未知模板" { t.Errorf("模板 %q 应有有效描述", tmpl) } } } func TestAllTemplatesValidate(t *testing.T) { for _, tmpl := range ValidTemplateNames() { p, err := NewFromTemplate("test-"+string(tmpl), tmpl) if err != nil { t.Fatalf("创建模板 %q 失败: %v", tmpl, err) } if err := p.Validate(); err != nil { t.Errorf("模板 %q 创建的 Profile 验证失败: %v", tmpl, err) } } }