Files
1jehuang--jcode/crates/jcode-base/src/auth/cursor_tests.rs
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

409 lines
12 KiB
Rust

use super::*;
use tempfile::TempDir;
#[test]
fn config_file_path_under_jcode() {
let path = config_file_path().unwrap();
let path_str = path.to_string_lossy();
assert!(path_str.contains("jcode"));
assert!(path_str.ends_with("cursor.env"));
}
#[test]
fn save_and_load_api_key() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("jcode").join("cursor.env");
std::fs::create_dir_all(file.parent().unwrap()).unwrap();
let content = "CURSOR_API_KEY=test_key_123\n";
std::fs::write(&file, content).unwrap();
let loaded = load_key_from_file(&file).unwrap();
assert_eq!(loaded, "test_key_123");
}
#[test]
fn load_key_quoted() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("cursor.env");
std::fs::write(&file, "CURSOR_API_KEY=\"my_quoted_key\"\n").unwrap();
let loaded = load_key_from_file(&file).unwrap();
assert_eq!(loaded, "my_quoted_key");
}
#[test]
fn load_key_single_quoted() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("cursor.env");
std::fs::write(&file, "CURSOR_API_KEY='single_quoted'\n").unwrap();
let loaded = load_key_from_file(&file).unwrap();
assert_eq!(loaded, "single_quoted");
}
#[test]
fn load_key_empty_value() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("cursor.env");
std::fs::write(&file, "CURSOR_API_KEY=\n").unwrap();
let result = load_key_from_file(&file);
assert!(result.is_err());
}
#[test]
fn load_key_missing_file() {
let path = PathBuf::from("/tmp/nonexistent_cursor_test_12345.env");
let result = load_key_from_file(&path);
assert!(result.is_err());
}
#[test]
fn load_key_no_cursor_line() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("cursor.env");
std::fs::write(&file, "OTHER_KEY=value\n").unwrap();
let result = load_key_from_file(&file);
assert!(result.is_err());
}
#[test]
fn load_key_with_whitespace() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("cursor.env");
std::fs::write(&file, " CURSOR_API_KEY= spaced_key \n").unwrap();
let loaded = load_key_from_file(&file).unwrap();
assert_eq!(loaded, "spaced_key");
}
#[test]
fn load_key_multiple_lines() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("cursor.env");
std::fs::write(
&file,
"# comment\nOTHER=foo\nCURSOR_API_KEY=the_real_key\nMORE=bar\n",
)
.unwrap();
let loaded = load_key_from_file(&file).unwrap();
assert_eq!(loaded, "the_real_key");
}
#[test]
fn has_cursor_api_key_from_env() {
let key = "CURSOR_API_KEY";
let guard = std::env::var(key).ok();
crate::env::set_var(key, "env_test_key");
let result = std::env::var(key).unwrap();
assert_eq!(result, "env_test_key");
match guard {
Some(v) => crate::env::set_var(key, v),
None => crate::env::remove_var(key),
}
}
#[test]
fn cursor_auth_file_path_respects_jcode_home() {
// Regression: on Linux the auth.json path previously used
// `dirs::config_dir()` directly, ignoring JCODE_HOME. That leaked the real
// `~/.config/cursor/auth.json` into the onboarding sandbox, so a
// fresh-install sandbox showed only Cursor as importable while every other
// provider correctly looked under `$JCODE_HOME/external/...`.
let _guard = crate::storage::lock_test_env();
let prev_home = std::env::var_os("JCODE_HOME");
let temp = TempDir::new().unwrap();
crate::env::set_var("JCODE_HOME", temp.path());
let path = cursor_auth_file_path().expect("cursor auth path");
assert!(
path.starts_with(temp.path().join("external")),
"cursor auth path should be under JCODE_HOME/external, got {}",
path.display()
);
if let Some(prev_home) = prev_home {
crate::env::set_var("JCODE_HOME", prev_home);
} else {
crate::env::remove_var("JCODE_HOME");
}
}
#[test]
fn cursor_vscdb_paths_respect_jcode_home() {
let _guard = crate::storage::lock_test_env();
let prev_home = std::env::var_os("JCODE_HOME");
let temp = TempDir::new().unwrap();
crate::env::set_var("JCODE_HOME", temp.path());
let paths = cursor_vscdb_paths();
assert!(!paths.is_empty());
for path in paths {
assert!(path.starts_with(temp.path().join("external")));
}
if let Some(prev_home) = prev_home {
crate::env::set_var("JCODE_HOME", prev_home);
} else {
crate::env::remove_var("JCODE_HOME");
}
}
#[test]
fn load_api_key_empty_env_falls_through() {
let key_str = "";
assert!(key_str.trim().is_empty());
}
#[cfg(unix)]
#[test]
fn load_access_token_from_auth_file_does_not_change_external_permissions() {
use std::os::unix::fs::PermissionsExt;
let _guard = crate::storage::lock_test_env();
let prev_home = std::env::var_os("JCODE_HOME");
let temp = TempDir::new().unwrap();
crate::env::set_var("JCODE_HOME", temp.path());
let path = cursor_auth_file_path().expect("cursor auth path");
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(
&path,
r#"{"accessToken":"at-test","refreshToken":"rt-test"}"#,
)
.unwrap();
std::fs::set_permissions(
path.parent().unwrap(),
std::fs::Permissions::from_mode(0o755),
)
.unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
crate::config::Config::allow_external_auth_source_for_path(CURSOR_AUTH_FILE_SOURCE_ID, &path)
.expect("trust cursor auth path");
let tokens = load_access_token_from_env_or_file().expect("load auth file token");
assert_eq!(tokens.access_token, "at-test");
assert_eq!(tokens.refresh_token.as_deref(), Some("rt-test"));
assert!(has_cursor_auth_file_token());
let dir_mode = std::fs::metadata(path.parent().unwrap())
.unwrap()
.permissions()
.mode()
& 0o777;
let file_mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
assert_eq!(dir_mode, 0o755);
assert_eq!(file_mode, 0o644);
if let Some(prev_home) = prev_home {
crate::env::set_var("JCODE_HOME", prev_home);
} else {
crate::env::remove_var("JCODE_HOME");
}
}
#[test]
fn status_output_detects_authenticated_session() {
assert!(status_output_indicates_authenticated(
true,
b"Authenticated\nAccount: user@example.com\nEndpoint: production",
b""
));
}
#[test]
fn status_output_detects_missing_authentication() {
assert!(!status_output_indicates_authenticated(
true,
b"Not authenticated. Run cursor-agent login.",
b""
));
}
#[test]
fn status_output_requires_successful_exit_for_authentication_keywords() {
assert!(!status_output_indicates_authenticated(
false,
b"Account: user@example.com\nEndpoint: production",
b"cursor-agent status failed"
));
}
#[cfg(unix)]
#[test]
fn external_auth_command_timeout_returns_none() {
let mut command = std::process::Command::new("sh");
command.arg("-c").arg("sleep 2; echo late");
let start = std::time::Instant::now();
let output = command_output_with_timeout(&mut command, std::time::Duration::from_millis(50))
.expect("timeout helper should not error");
assert!(output.is_none());
assert!(start.elapsed() < std::time::Duration::from_secs(1));
}
fn load_key_from_file(path: &PathBuf) -> Result<String> {
if !path.exists() {
anyhow::bail!("File not found");
}
let content = std::fs::read_to_string(path)?;
for line in content.lines() {
let line = line.trim();
if let Some(key) = line.strip_prefix("CURSOR_API_KEY=") {
let key = key.trim().trim_matches('"').trim_matches('\'');
if !key.is_empty() {
return Ok(key.to_string());
}
}
}
anyhow::bail!("No CURSOR_API_KEY found")
}
/// Helper: create a mock state.vscdb with the given key/value pairs.
fn create_mock_vscdb(dir: &std::path::Path, entries: &[(&str, &str)]) -> PathBuf {
let db_path = dir.join("state.vscdb");
let status = std::process::Command::new("sqlite3")
.arg(&db_path)
.arg("CREATE TABLE ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB);")
.status()
.expect("sqlite3 must be installed for these tests");
assert!(status.success(), "Failed to create mock vscdb");
for (key, value) in entries {
let sql = format!(
"INSERT INTO ItemTable (key, value) VALUES ('{}', '{}');",
key, value
);
let status = std::process::Command::new("sqlite3")
.arg(&db_path)
.arg(&sql)
.status()
.unwrap();
assert!(status.success(), "Failed to insert into mock vscdb");
}
db_path
}
#[test]
fn vscdb_read_access_token() {
let dir = TempDir::new().unwrap();
let db = create_mock_vscdb(dir.path(), &[("cursorAuth/accessToken", "tok_abc123xyz")]);
let result = read_vscdb_key(&db, "cursorAuth/accessToken").unwrap();
assert_eq!(result, "tok_abc123xyz");
}
#[test]
fn vscdb_read_machine_id() {
let dir = TempDir::new().unwrap();
let db = create_mock_vscdb(
dir.path(),
&[(
"storage.serviceMachineId",
"550e8400-e29b-41d4-a716-446655440000",
)],
);
let result = read_vscdb_key(&db, "storage.serviceMachineId").unwrap();
assert_eq!(result, "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn vscdb_missing_key_returns_error() {
let dir = TempDir::new().unwrap();
let db = create_mock_vscdb(dir.path(), &[("other/key", "value")]);
let result = read_vscdb_key(&db, "cursorAuth/accessToken");
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains("not found or empty")
);
}
#[test]
fn vscdb_empty_value_returns_error() {
let dir = TempDir::new().unwrap();
let db = create_mock_vscdb(dir.path(), &[("cursorAuth/accessToken", "")]);
let result = read_vscdb_key(&db, "cursorAuth/accessToken");
assert!(result.is_err());
}
#[test]
fn vscdb_missing_file_returns_error() {
let path = PathBuf::from("/tmp/nonexistent_vscdb_test_999.vscdb");
let result = read_vscdb_key(&path, "cursorAuth/accessToken");
assert!(result.is_err());
}
#[test]
fn vscdb_multiple_keys() {
let dir = TempDir::new().unwrap();
let db = create_mock_vscdb(
dir.path(),
&[
("cursorAuth/accessToken", "my_token"),
("storage.serviceMachineId", "machine_123"),
("cursorAuth/refreshToken", "refresh_456"),
("cursorAuth/cachedEmail", "user@example.com"),
],
);
assert_eq!(
read_vscdb_key(&db, "cursorAuth/accessToken").unwrap(),
"my_token"
);
assert_eq!(
read_vscdb_key(&db, "storage.serviceMachineId").unwrap(),
"machine_123"
);
assert_eq!(
read_vscdb_key(&db, "cursorAuth/refreshToken").unwrap(),
"refresh_456"
);
assert_eq!(
read_vscdb_key(&db, "cursorAuth/cachedEmail").unwrap(),
"user@example.com"
);
}
#[test]
fn vscdb_wrong_table_name() {
let dir = TempDir::new().unwrap();
let db_path = dir.path().join("state.vscdb");
let status = std::process::Command::new("sqlite3")
.arg(&db_path)
.arg("CREATE TABLE WrongTable (key TEXT, value BLOB);")
.status()
.unwrap();
assert!(status.success());
let result = read_vscdb_key(&db_path, "cursorAuth/accessToken");
assert!(result.is_err());
}
#[test]
fn vscdb_paths_not_empty() {
let paths = cursor_vscdb_paths();
assert!(!paths.is_empty(), "Should have at least one candidate path");
for path in &paths {
let s = path.to_string_lossy();
assert!(
s.contains("ursor"),
"Path should contain 'Cursor' or 'cursor'"
);
assert!(s.ends_with("state.vscdb"));
}
}
#[test]
fn find_vscdb_missing_returns_error() {
let result = find_cursor_vscdb();
// On this machine Cursor isn't installed, so it should fail
// (if Cursor IS installed, this test still passes - it finds the file)
if let Err(err) = result {
assert!(err.to_string().contains("not found"));
}
}