新增profile模块
This commit is contained in:
427
internal/profile/profile_test.go
Normal file
427
internal/profile/profile_test.go
Normal file
@@ -0,0 +1,427 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user