完成一个简易的全局skill、command管理器
This commit is contained in:
141
manager/internal/config/install.go
Normal file
141
manager/internal/config/install.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"skillmgr/internal/types"
|
||||
)
|
||||
|
||||
// LoadInstallConfig 加载安装配置
|
||||
func LoadInstallConfig() (*types.InstallConfig, error) {
|
||||
path, err := GetInstallConfigPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return &types.InstallConfig{
|
||||
Installations: []types.InstallRecord{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg types.InstallConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("解析 install.json 失败: %w(请检查 JSON 格式)", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// SaveInstallConfig 保存安装配置
|
||||
func SaveInstallConfig(cfg *types.InstallConfig) error {
|
||||
path, err := GetInstallConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// AddInstallRecord 添加安装记录
|
||||
func AddInstallRecord(record types.InstallRecord) error {
|
||||
cfg, err := LoadInstallConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.Installations = append(cfg.Installations, record)
|
||||
return SaveInstallConfig(cfg)
|
||||
}
|
||||
|
||||
// RemoveInstallRecord 移除安装记录
|
||||
func RemoveInstallRecord(itemType types.ItemType, name string, platform types.Platform, scope types.Scope) error {
|
||||
cfg, err := LoadInstallConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, r := range cfg.Installations {
|
||||
if r.Type == itemType && r.Name == name && r.Platform == platform && r.Scope == scope {
|
||||
cfg.Installations = append(cfg.Installations[:i], cfg.Installations[i+1:]...)
|
||||
return SaveInstallConfig(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindInstallRecord 查找安装记录
|
||||
func FindInstallRecord(itemType types.ItemType, name string, platform types.Platform, scope types.Scope) (*types.InstallRecord, error) {
|
||||
cfg, err := LoadInstallConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range cfg.Installations {
|
||||
if r.Type == itemType && r.Name == name && r.Platform == platform && r.Scope == scope {
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// UpdateInstallRecord 更新安装记录
|
||||
func UpdateInstallRecord(record types.InstallRecord) error {
|
||||
cfg, err := LoadInstallConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, r := range cfg.Installations {
|
||||
if r.Type == record.Type && r.Name == record.Name &&
|
||||
r.Platform == record.Platform && r.Scope == record.Scope {
|
||||
cfg.Installations[i] = record
|
||||
return SaveInstallConfig(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanOrphanRecords 清理孤立记录(安装路径不存在)
|
||||
func CleanOrphanRecords() ([]types.InstallRecord, error) {
|
||||
cfg, err := LoadInstallConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 预分配切片容量,减少内存分配次数
|
||||
cleaned := make([]types.InstallRecord, 0, len(cfg.Installations)/2)
|
||||
valid := make([]types.InstallRecord, 0, len(cfg.Installations))
|
||||
|
||||
for _, r := range cfg.Installations {
|
||||
if _, err := os.Stat(r.InstallPath); os.IsNotExist(err) {
|
||||
cleaned = append(cleaned, r)
|
||||
} else {
|
||||
valid = append(valid, r)
|
||||
}
|
||||
}
|
||||
|
||||
if len(cleaned) > 0 {
|
||||
cfg.Installations = valid
|
||||
if err := SaveInstallConfig(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned, nil
|
||||
}
|
||||
169
manager/internal/config/install_test.go
Normal file
169
manager/internal/config/install_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"skillmgr/internal/types"
|
||||
)
|
||||
|
||||
func setupInstallTestEnv(t *testing.T) (string, func()) {
|
||||
t.Helper()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "skillmgr-install-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("创建临时目录失败: %v", err)
|
||||
}
|
||||
|
||||
os.Setenv("SKILLMGR_TEST_ROOT", tmpDir)
|
||||
|
||||
cleanup := func() {
|
||||
os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||||
os.RemoveAll(tmpDir)
|
||||
}
|
||||
|
||||
return tmpDir, cleanup
|
||||
}
|
||||
|
||||
func TestLoadInstallConfig_Empty(t *testing.T) {
|
||||
_, cleanup := setupInstallTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
cfg, err := LoadInstallConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadInstallConfig 失败: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.Installations) != 0 {
|
||||
t.Errorf("期望空安装列表,得到 %d 个", len(cfg.Installations))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddInstallRecord_Success(t *testing.T) {
|
||||
_, cleanup := setupInstallTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
record := types.InstallRecord{
|
||||
Type: types.ItemTypeSkill,
|
||||
Name: "test-skill",
|
||||
SourceRepo: "test-repo",
|
||||
Platform: types.PlatformClaude,
|
||||
Scope: types.ScopeGlobal,
|
||||
InstallPath: "/path/to/skill",
|
||||
InstalledAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := AddInstallRecord(record); err != nil {
|
||||
t.Fatalf("AddInstallRecord 失败: %v", err)
|
||||
}
|
||||
|
||||
cfg, _ := LoadInstallConfig()
|
||||
if len(cfg.Installations) != 1 {
|
||||
t.Errorf("期望 1 条记录,得到 %d 条", len(cfg.Installations))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindInstallRecord_Found(t *testing.T) {
|
||||
_, cleanup := setupInstallTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
record := types.InstallRecord{
|
||||
Type: types.ItemTypeSkill,
|
||||
Name: "test-skill",
|
||||
Platform: types.PlatformClaude,
|
||||
Scope: types.ScopeGlobal,
|
||||
InstalledAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
AddInstallRecord(record)
|
||||
|
||||
found, err := FindInstallRecord(types.ItemTypeSkill, "test-skill", types.PlatformClaude, types.ScopeGlobal)
|
||||
if err != nil {
|
||||
t.Fatalf("FindInstallRecord 失败: %v", err)
|
||||
}
|
||||
|
||||
if found.Name != "test-skill" {
|
||||
t.Errorf("期望名称 'test-skill',得到 '%s'", found.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveInstallRecord_Success(t *testing.T) {
|
||||
_, cleanup := setupInstallTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
record := types.InstallRecord{
|
||||
Type: types.ItemTypeSkill,
|
||||
Name: "test-skill",
|
||||
Platform: types.PlatformClaude,
|
||||
Scope: types.ScopeGlobal,
|
||||
InstalledAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
AddInstallRecord(record)
|
||||
|
||||
if err := RemoveInstallRecord(types.ItemTypeSkill, "test-skill", types.PlatformClaude, types.ScopeGlobal); err != nil {
|
||||
t.Fatalf("RemoveInstallRecord 失败: %v", err)
|
||||
}
|
||||
|
||||
cfg, _ := LoadInstallConfig()
|
||||
if len(cfg.Installations) != 0 {
|
||||
t.Errorf("期望 0 条记录,得到 %d 条", len(cfg.Installations))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanOrphanRecords(t *testing.T) {
|
||||
tmpDir, cleanup := setupInstallTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
// 创建一个存在的路径
|
||||
existingPath := filepath.Join(tmpDir, "existing-skill")
|
||||
os.MkdirAll(existingPath, 0755)
|
||||
|
||||
// 添加两条记录:一条存在,一条不存在
|
||||
record1 := types.InstallRecord{
|
||||
Type: types.ItemTypeSkill,
|
||||
Name: "existing-skill",
|
||||
Platform: types.PlatformClaude,
|
||||
Scope: types.ScopeGlobal,
|
||||
InstallPath: existingPath,
|
||||
InstalledAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
record2 := types.InstallRecord{
|
||||
Type: types.ItemTypeSkill,
|
||||
Name: "orphan-skill",
|
||||
Platform: types.PlatformClaude,
|
||||
Scope: types.ScopeGlobal,
|
||||
InstallPath: "/nonexistent/path",
|
||||
InstalledAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
AddInstallRecord(record1)
|
||||
AddInstallRecord(record2)
|
||||
|
||||
cleaned, err := CleanOrphanRecords()
|
||||
if err != nil {
|
||||
t.Fatalf("CleanOrphanRecords 失败: %v", err)
|
||||
}
|
||||
|
||||
if len(cleaned) != 1 {
|
||||
t.Errorf("期望清理 1 条记录,清理了 %d 条", len(cleaned))
|
||||
}
|
||||
|
||||
if len(cleaned) > 0 && cleaned[0].Name != "orphan-skill" {
|
||||
t.Errorf("期望清理 'orphan-skill',清理了 '%s'", cleaned[0].Name)
|
||||
}
|
||||
|
||||
// 验证只剩下存在的记录
|
||||
cfg, _ := LoadInstallConfig()
|
||||
if len(cfg.Installations) != 1 {
|
||||
t.Errorf("期望剩余 1 条记录,剩余 %d 条", len(cfg.Installations))
|
||||
}
|
||||
}
|
||||
77
manager/internal/config/paths.go
Normal file
77
manager/internal/config/paths.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigDir = ".skillmgr"
|
||||
RepositoryFile = "repository.json"
|
||||
InstallFile = "install.json"
|
||||
CacheDir = "cache"
|
||||
)
|
||||
|
||||
// GetConfigRoot 获取配置根目录
|
||||
// 支持通过环境变量 SKILLMGR_TEST_ROOT 覆盖(用于测试隔离)
|
||||
func GetConfigRoot() (string, error) {
|
||||
// 测试模式:使用环境变量指定的临时目录
|
||||
if testRoot := os.Getenv("SKILLMGR_TEST_ROOT"); testRoot != "" {
|
||||
return testRoot, nil
|
||||
}
|
||||
|
||||
// 生产模式:使用用户主目录
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(home, ConfigDir), nil
|
||||
}
|
||||
|
||||
// GetRepositoryConfigPath 获取 repository.json 路径
|
||||
func GetRepositoryConfigPath() (string, error) {
|
||||
root, err := GetConfigRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(root, RepositoryFile), nil
|
||||
}
|
||||
|
||||
// GetInstallConfigPath 获取 install.json 路径
|
||||
func GetInstallConfigPath() (string, error) {
|
||||
root, err := GetConfigRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(root, InstallFile), nil
|
||||
}
|
||||
|
||||
// GetCachePath 获取缓存目录路径
|
||||
func GetCachePath() (string, error) {
|
||||
root, err := GetConfigRoot()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(root, CacheDir), nil
|
||||
}
|
||||
|
||||
// EnsureConfigDirs 确保配置目录存在
|
||||
func EnsureConfigDirs() error {
|
||||
root, err := GetConfigRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dirs := []string{
|
||||
root,
|
||||
filepath.Join(root, CacheDir),
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
60
manager/internal/config/paths_test.go
Normal file
60
manager/internal/config/paths_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetConfigRoot_Default(t *testing.T) {
|
||||
// 清除环境变量
|
||||
os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||||
|
||||
root, err := GetConfigRoot()
|
||||
if err != nil {
|
||||
t.Fatalf("GetConfigRoot 失败: %v", err)
|
||||
}
|
||||
|
||||
home, _ := os.UserHomeDir()
|
||||
expected := filepath.Join(home, ConfigDir)
|
||||
|
||||
if root != expected {
|
||||
t.Errorf("期望 %s,得到 %s", expected, root)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfigRoot_WithEnvOverride(t *testing.T) {
|
||||
testRoot := "/tmp/skillmgr-test"
|
||||
os.Setenv("SKILLMGR_TEST_ROOT", testRoot)
|
||||
defer os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||||
|
||||
root, err := GetConfigRoot()
|
||||
if err != nil {
|
||||
t.Fatalf("GetConfigRoot 失败: %v", err)
|
||||
}
|
||||
|
||||
if root != testRoot {
|
||||
t.Errorf("期望 %s,得到 %s", testRoot, root)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureConfigDirs(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "skillmgr-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("创建临时目录失败: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
os.Setenv("SKILLMGR_TEST_ROOT", tmpDir)
|
||||
defer os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||||
|
||||
if err := EnsureConfigDirs(); err != nil {
|
||||
t.Fatalf("EnsureConfigDirs 失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查目录是否存在
|
||||
cacheDir := filepath.Join(tmpDir, CacheDir)
|
||||
if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
|
||||
t.Errorf("缓存目录未创建: %s", cacheDir)
|
||||
}
|
||||
}
|
||||
105
manager/internal/config/repository.go
Normal file
105
manager/internal/config/repository.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"skillmgr/internal/types"
|
||||
)
|
||||
|
||||
// LoadRepositoryConfig 加载仓库配置
|
||||
func LoadRepositoryConfig() (*types.RepositoryConfig, error) {
|
||||
path, err := GetRepositoryConfigPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果文件不存在,返回空配置
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return &types.RepositoryConfig{
|
||||
Repositories: []types.Repository{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg types.RepositoryConfig
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, fmt.Errorf("解析 repository.json 失败: %w(请检查 JSON 格式)", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// SaveRepositoryConfig 保存仓库配置
|
||||
func SaveRepositoryConfig(cfg *types.RepositoryConfig) error {
|
||||
path, err := GetRepositoryConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, 0644)
|
||||
}
|
||||
|
||||
// AddRepository 添加仓库
|
||||
// 如果仓库名已存在,返回错误提示先移除
|
||||
func AddRepository(repo types.Repository) error {
|
||||
cfg, err := LoadRepositoryConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否已存在同名仓库
|
||||
for _, r := range cfg.Repositories {
|
||||
if r.Name == repo.Name {
|
||||
return fmt.Errorf("仓库名称 '%s' 已存在,请先使用 `skillmgr remove %s` 移除", repo.Name, repo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// 新增
|
||||
cfg.Repositories = append(cfg.Repositories, repo)
|
||||
return SaveRepositoryConfig(cfg)
|
||||
}
|
||||
|
||||
// RemoveRepository 移除仓库
|
||||
func RemoveRepository(name string) error {
|
||||
cfg, err := LoadRepositoryConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, r := range cfg.Repositories {
|
||||
if r.Name == name {
|
||||
cfg.Repositories = append(cfg.Repositories[:i], cfg.Repositories[i+1:]...)
|
||||
return SaveRepositoryConfig(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// 仓库不存在,不报错
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindRepository 查找仓库
|
||||
func FindRepository(name string) (*types.Repository, error) {
|
||||
cfg, err := LoadRepositoryConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, r := range cfg.Repositories {
|
||||
if r.Name == name {
|
||||
return &r, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
159
manager/internal/config/repository_test.go
Normal file
159
manager/internal/config/repository_test.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"skillmgr/internal/types"
|
||||
)
|
||||
|
||||
func setupRepoTestEnv(t *testing.T) (string, func()) {
|
||||
t.Helper()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "skillmgr-repo-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("创建临时目录失败: %v", err)
|
||||
}
|
||||
|
||||
os.Setenv("SKILLMGR_TEST_ROOT", tmpDir)
|
||||
|
||||
cleanup := func() {
|
||||
os.Unsetenv("SKILLMGR_TEST_ROOT")
|
||||
os.RemoveAll(tmpDir)
|
||||
}
|
||||
|
||||
return tmpDir, cleanup
|
||||
}
|
||||
|
||||
func TestLoadRepositoryConfig_Empty(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
cfg, err := LoadRepositoryConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("LoadRepositoryConfig 失败: %v", err)
|
||||
}
|
||||
|
||||
if len(cfg.Repositories) != 0 {
|
||||
t.Errorf("期望空仓库列表,得到 %d 个", len(cfg.Repositories))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRepository_Success(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
repo := types.Repository{
|
||||
Name: "test-repo",
|
||||
URL: "https://github.com/test/repo.git",
|
||||
Branch: "main",
|
||||
AddedAt: time.Now(),
|
||||
}
|
||||
|
||||
if err := AddRepository(repo); err != nil {
|
||||
t.Fatalf("AddRepository 失败: %v", err)
|
||||
}
|
||||
|
||||
// 验证已添加
|
||||
cfg, _ := LoadRepositoryConfig()
|
||||
if len(cfg.Repositories) != 1 {
|
||||
t.Errorf("期望 1 个仓库,得到 %d 个", len(cfg.Repositories))
|
||||
}
|
||||
|
||||
if cfg.Repositories[0].Name != "test-repo" {
|
||||
t.Errorf("期望名称 'test-repo',得到 '%s'", cfg.Repositories[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRepository_RejectDuplicate(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
repo := types.Repository{
|
||||
Name: "test-repo",
|
||||
URL: "https://github.com/test/repo.git",
|
||||
Branch: "main",
|
||||
AddedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 第一次添加
|
||||
if err := AddRepository(repo); err != nil {
|
||||
t.Fatalf("第一次 AddRepository 失败: %v", err)
|
||||
}
|
||||
|
||||
// 第二次添加应该失败
|
||||
err := AddRepository(repo)
|
||||
if err == nil {
|
||||
t.Error("期望添加重复仓库时返回错误")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRepository_Success(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
repo := types.Repository{
|
||||
Name: "test-repo",
|
||||
URL: "https://github.com/test/repo.git",
|
||||
AddedAt: time.Now(),
|
||||
}
|
||||
|
||||
AddRepository(repo)
|
||||
|
||||
if err := RemoveRepository("test-repo"); err != nil {
|
||||
t.Fatalf("RemoveRepository 失败: %v", err)
|
||||
}
|
||||
|
||||
cfg, _ := LoadRepositoryConfig()
|
||||
if len(cfg.Repositories) != 0 {
|
||||
t.Errorf("期望 0 个仓库,得到 %d 个", len(cfg.Repositories))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveRepository_NotFound(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
// RemoveRepository 实现中,不存在的仓库不报错
|
||||
err := RemoveRepository("nonexistent")
|
||||
if err != nil {
|
||||
t.Errorf("RemoveRepository 不应该报错: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindRepository_Found(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
repo := types.Repository{
|
||||
Name: "test-repo",
|
||||
URL: "https://github.com/test/repo.git",
|
||||
AddedAt: time.Now(),
|
||||
}
|
||||
|
||||
AddRepository(repo)
|
||||
|
||||
found, err := FindRepository("test-repo")
|
||||
if err != nil {
|
||||
t.Fatalf("FindRepository 失败: %v", err)
|
||||
}
|
||||
|
||||
if found == nil || found.Name != "test-repo" {
|
||||
t.Errorf("期望找到 'test-repo'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindRepository_NotFound(t *testing.T) {
|
||||
_, cleanup := setupRepoTestEnv(t)
|
||||
defer cleanup()
|
||||
|
||||
// FindRepository 实现中,找不到时返回 nil, nil
|
||||
found, err := FindRepository("nonexistent")
|
||||
if err != nil {
|
||||
t.Errorf("FindRepository 不应该报错: %v", err)
|
||||
}
|
||||
if found != nil {
|
||||
t.Errorf("期望返回 nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user