2026-11-10 01:24:28 +08:00
|
|
|
package cursor
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"database/sql"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestProjectPathFromURI(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
name string
|
|
|
|
|
uri string
|
|
|
|
|
want string
|
|
|
|
|
}{
|
|
|
|
|
{"windows 盘符", "file:///d:/Project/x", "d:/Project/x"},
|
|
|
|
|
{"编码盘符", "file:///d%3A/Project/x", "d:/Project/x"},
|
|
|
|
|
{"带空格编码", "file:///d:/My%20Projects/x", "d:/My Projects/x"},
|
|
|
|
|
{"posix 路径", "file:///home/user/x", "/home/user/x"},
|
|
|
|
|
{"非 file URI", "vscode-remote://ssh-remote/home/x", ""},
|
|
|
|
|
{"普通路径", "d:/Project/x", ""},
|
|
|
|
|
{"空串", "", ""},
|
|
|
|
|
}
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
if got := projectPathFromURI(tc.uri); got != tc.want {
|
|
|
|
|
t.Fatalf("projectPathFromURI(%q) = %q, want %q", tc.uri, got, tc.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// createStateDB 在 userDir/globalStorage 下创建含最近打开列表的 state.vscdb。
|
|
|
|
|
func createStateDB(t *testing.T, userDir string, uris []string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if err := os.MkdirAll(filepath.Join(userDir, "globalStorage"), 0o755); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
db, err := sql.Open("sqlite", filepath.Join(userDir, "globalStorage", "state.vscdb"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer db.Close()
|
|
|
|
|
if _, err := db.Exec("CREATE TABLE ItemTable (key TEXT UNIQUE, value TEXT)"); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
entries := make([]map[string]string, 0, len(uris))
|
|
|
|
|
for _, uri := range uris {
|
|
|
|
|
entries = append(entries, map[string]string{"folderUri": uri})
|
|
|
|
|
}
|
|
|
|
|
raw, _ := json.Marshal(map[string]any{"entries": entries})
|
|
|
|
|
if _, err := db.Exec(
|
|
|
|
|
"INSERT INTO ItemTable (key, value) VALUES ('history.recentlyOpenedPathsList', ?)", string(raw),
|
|
|
|
|
); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func readRecentlyOpened(t *testing.T, userDir string) []string {
|
|
|
|
|
t.Helper()
|
|
|
|
|
db, err := sql.Open("sqlite", filepath.Join(userDir, "globalStorage", "state.vscdb"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer db.Close()
|
|
|
|
|
var raw string
|
|
|
|
|
if err := db.QueryRow(
|
|
|
|
|
"SELECT value FROM ItemTable WHERE key = 'history.recentlyOpenedPathsList'",
|
|
|
|
|
).Scan(&raw); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var payload struct {
|
|
|
|
|
Entries []struct {
|
|
|
|
|
FolderURI string `json:"folderUri"`
|
|
|
|
|
} `json:"entries"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal([]byte(raw), &payload); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
result := make([]string, 0, len(payload.Entries))
|
|
|
|
|
for _, item := range payload.Entries {
|
|
|
|
|
result = append(result, item.FolderURI)
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRemoveRecentlyOpenedEntry(t *testing.T) {
|
|
|
|
|
t.Run("命中条目被移除", func(t *testing.T) {
|
|
|
|
|
userDir := t.TempDir()
|
|
|
|
|
createStateDB(t, userDir, []string{"file:///d:/Proj/A", "file:///d:/Proj/B"})
|
|
|
|
|
removed, err := removeRecentlyOpenedEntry(userDir, "d:/Proj/A")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if removed != 1 {
|
|
|
|
|
t.Fatalf("removed = %d, want 1", removed)
|
|
|
|
|
}
|
|
|
|
|
remaining := readRecentlyOpened(t, userDir)
|
|
|
|
|
if len(remaining) != 1 || remaining[0] != "file:///d:/Proj/B" {
|
|
|
|
|
t.Fatalf("remaining = %v, want [file:///d:/Proj/B]", remaining)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("未命中不写库", func(t *testing.T) {
|
|
|
|
|
userDir := t.TempDir()
|
|
|
|
|
createStateDB(t, userDir, []string{"file:///d:/Proj/A"})
|
|
|
|
|
removed, err := removeRecentlyOpenedEntry(userDir, "d:/Proj/Other")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if removed != 0 {
|
|
|
|
|
t.Fatalf("removed = %d, want 0", removed)
|
|
|
|
|
}
|
|
|
|
|
if got := readRecentlyOpened(t, userDir); len(got) != 1 {
|
|
|
|
|
t.Fatalf("remaining = %v, want 1 entry", got)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("缺失数据库返回零值无错误", func(t *testing.T) {
|
|
|
|
|
removed, err := removeRecentlyOpenedEntry(t.TempDir(), "d:/Proj/A")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if removed != 0 {
|
|
|
|
|
t.Fatalf("removed = %d, want 0", removed)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("损坏数据库返回错误", func(t *testing.T) {
|
|
|
|
|
userDir := t.TempDir()
|
|
|
|
|
if err := os.MkdirAll(filepath.Join(userDir, "globalStorage"), 0o755); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if err := os.WriteFile(
|
|
|
|
|
filepath.Join(userDir, "globalStorage", "state.vscdb"), []byte("not a db"), 0o644,
|
|
|
|
|
); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := removeRecentlyOpenedEntry(userDir, "d:/Proj/A"); err == nil {
|
|
|
|
|
t.Fatal("want error for corrupt db, got nil")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRemoveWorkspaceStorageEntries(t *testing.T) {
|
|
|
|
|
t.Run("命中目录被整目录删除", func(t *testing.T) {
|
|
|
|
|
userDir := t.TempDir()
|
|
|
|
|
target := filepath.Join(userDir, "workspaceStorage", "hash-a")
|
|
|
|
|
other := filepath.Join(userDir, "workspaceStorage", "hash-b")
|
|
|
|
|
for dir, folder := range map[string]string{
|
|
|
|
|
target: "file:///d:/Proj/A",
|
|
|
|
|
other: "file:///d:/Proj/B",
|
|
|
|
|
} {
|
|
|
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
raw, _ := json.Marshal(map[string]string{"folder": folder})
|
|
|
|
|
if err := os.WriteFile(filepath.Join(dir, "workspace.json"), raw, 0o644); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
removed, err := removeWorkspaceStorageEntries(userDir, "d:/Proj/A")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if removed != 1 {
|
|
|
|
|
t.Fatalf("removed = %d, want 1", removed)
|
|
|
|
|
}
|
|
|
|
|
if _, err := os.Stat(target); !os.IsNotExist(err) {
|
|
|
|
|
t.Fatalf("target dir should be removed, stat err = %v", err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := os.Stat(other); err != nil {
|
|
|
|
|
t.Fatalf("other dir should remain: %v", err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("缺失 workspaceStorage 返回零值无错误", func(t *testing.T) {
|
|
|
|
|
removed, err := removeWorkspaceStorageEntries(t.TempDir(), "d:/Proj/A")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("unexpected error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if removed != 0 {
|
|
|
|
|
t.Fatalf("removed = %d, want 0", removed)
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-11-10 09:59:34 +08:00
|
|
|
}
|