168 lines
5.4 KiB
Go
168 lines
5.4 KiB
Go
|
|
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")
|
||
|
|
}
|
||
|
|
}
|