package cursor import ( "bytes" "database/sql" "encoding/json" "errors" "os" "path/filepath" "testing" "cursor/internal/certs" ) func isolatedHome(t *testing.T) string { t.Helper() dir := t.TempDir() for _, key := range []string{"HOME", "USERPROFILE", "APPDATA", "XDG_CONFIG_HOME"} { t.Setenv(key, dir) } return dir } func TestProxySettingsRestoreAndPreserveConcurrentUserChange(t *testing.T) { isolatedHome(t) path, err := resolveCursorSettingsPath() if err != nil { t.Fatal(err) } if err = os.MkdirAll(filepath.Dir(path), 0700); err != nil { t.Fatal(err) } original := []byte(`{"http.proxy":"http://original:8080","editor.fontSize":15,"custom":{"enabled":true}}`) if err = os.WriteFile(path, original, 0600); err != nil { t.Fatal(err) } for range 2 { if err = WriteUserProxySettings("http://127.0.0.1:28080"); err != nil { t.Fatal(err) } } current, err := loadSettings(path) if err != nil { t.Fatal(err) } current["http.proxySupport"] = json.RawMessage(`"override"`) data, _ := json.Marshal(current) if err = os.WriteFile(path, data, 0600); err != nil { t.Fatal(err) } if err = ClearUserProxySettings(); err != nil { t.Fatal(err) } got, err := loadSettings(path) if err != nil { t.Fatal(err) } if !rawEqual(got["http.proxy"], json.RawMessage(`"http://original:8080"`)) { t.Fatal("original proxy not restored") } if !rawEqual(got["http.proxySupport"], json.RawMessage(`"override"`)) { t.Fatal("user edit overwritten") } if !rawEqual(got["custom"], json.RawMessage(`{"enabled":true}`)) { t.Fatal("unrelated settings lost") } if _, ok := got["cursor.general.disableHttp2"]; ok { t.Fatal("injected key not removed") } if err = ClearUserProxySettings(); err != nil { t.Fatal(err) } } func TestInvalidSettingsNeverDeleted(t *testing.T) { isolatedHome(t) path, _ := resolveCursorSettingsPath() _ = os.MkdirAll(filepath.Dir(path), 0700) bad := []byte("{broken user settings // keep me") if err := os.WriteFile(path, bad, 0600); err != nil { t.Fatal(err) } if err := WriteUserProxySettings("http://127.0.0.1:28080"); err == nil { t.Fatal("invalid settings accepted") } after, _ := os.ReadFile(path) if !bytes.Equal(bad, after) { t.Fatal("user settings destroyed") } } func TestCertificateMigrationCancellationAndRetry(t *testing.T) { newCert := []byte("local CA") current, legacy := false, true trusted := func(cert []byte) (bool, error) { if bytes.Equal(cert, certs.EmbeddedCACertPEM()) { return legacy, nil } return current, nil } denied := errors.New("authorization canceled") if err := migrateCATrust(newCert, trusted, func() error { return denied }); !errors.Is(err, denied) { t.Fatal(err) } if current || !legacy { t.Fatal("cancellation changed trust") } if err := migrateCATrust(newCert, trusted, func() error { current = true; legacy = false; return nil }); err != nil { t.Fatal(err) } if err := migrateCATrust(newCert, trusted, func() error { t.Fatal("migration not idempotent"); return nil }); err != nil { t.Fatal(err) } legacy = true if err := migrateCATrust(newCert, trusted, func() error { return nil }); err == nil { t.Fatal("legacy trust silently retained") } } func TestRecentlyOpenedPreservesUnknownFields(t *testing.T) { dir := t.TempDir() createStateDB(t, dir, []string{"file:///d:/Project/A", "file:///d:/Project/B"}) db, err := sql.Open("sqlite", filepath.Join(dir, "globalStorage", "state.vscdb")) if err != nil { t.Fatal(err) } defer db.Close() raw := `{"version":3,"metadata":{"keep":true},"entries":[{"folderUri":"file:///d:/Project/A","label":"remove"},{"folderUri":"file:///d:/Project/B","label":"keep","remoteAuthority":"ssh-remote+host"},{"fileUri":"file:///d:/readme.md","label":"file"}]}` if _, err = db.Exec("UPDATE ItemTable SET value = ? WHERE key = 'history.recentlyOpenedPathsList'", raw); err != nil { t.Fatal(err) } if n, err := removeRecentlyOpenedEntry(dir, "d:/Project/A"); err != nil || n != 1 { t.Fatalf("removed=%d err=%v", n, err) } var updated string if err = db.QueryRow("SELECT value FROM ItemTable WHERE key = 'history.recentlyOpenedPathsList'").Scan(&updated); err != nil { t.Fatal(err) } var got map[string]json.RawMessage if err = json.Unmarshal([]byte(updated), &got); err != nil { t.Fatal(err) } if !rawEqual(got["metadata"], json.RawMessage(`{"keep":true}`)) || !rawEqual(got["version"], json.RawMessage(`3`)) { t.Fatal("top-level metadata lost") } var entries []map[string]any _ = json.Unmarshal(got["entries"], &entries) if len(entries) != 2 || entries[0]["remoteAuthority"] != "ssh-remote+host" || entries[1]["fileUri"] != "file:///d:/readme.md" { t.Fatalf("unmatched entries changed: %s", updated) } } func TestDatabaseFailurePreservesWorkspaceChatData(t *testing.T) { dir := t.TempDir() workspace := filepath.Join(dir, "workspaceStorage", "hash") _ = os.MkdirAll(workspace, 0700) _ = os.WriteFile(filepath.Join(workspace, "workspace.json"), []byte(`{"folder":"file:///d:/Project/A"}`), 0600) _ = os.WriteFile(filepath.Join(workspace, "chat.db"), []byte("important user chat"), 0600) _ = os.MkdirAll(filepath.Join(dir, "globalStorage"), 0700) _ = os.WriteFile(filepath.Join(dir, "globalStorage", "state.vscdb"), []byte("corrupt database"), 0600) if err := deleteProjectRecords(dir, "d:/Project/A"); err == nil { t.Fatal("corrupt database accepted") } if data, err := os.ReadFile(filepath.Join(workspace, "chat.db")); err != nil || string(data) != "important user chat" { t.Fatal("chat data removed before database update") } }