use anyhow::{Context, Result};
const SERVICE_NAME: &str = "linear-cli";
pub fn get_key(profile: &str) -> Result<Option<String>> {
let entry =
keyring::Entry::new(SERVICE_NAME, profile).context("Failed to create keyring entry")?;
match entry.get_password() {
Ok(password) => Ok(Some(password)),
Err(keyring::Error::NoEntry) => Ok(None),
Err(keyring::Error::NoStorageAccess(_)) => {
if !crate::output::is_quiet() {
eprintln!("Warning: Keyring not available, falling back to config file");
}
Ok(None)
}
Err(e) => {
if !crate::output::is_quiet() {
eprintln!(
"Warning: Keyring error ({}), falling back to config file",
e
);
}
Ok(None)
}
}
}
pub fn set_key(profile: &str, api_key: &str) -> Result<()> {
let entry =
keyring::Entry::new(SERVICE_NAME, profile).context("Failed to create keyring entry")?;
entry
.set_password(api_key)
.context("Failed to store API key in keyring")?;
Ok(())
}
pub fn delete_key(profile: &str) -> Result<()> {
let entry =
keyring::Entry::new(SERVICE_NAME, profile).context("Failed to create keyring entry")?;
match entry.delete_credential() {
Ok(()) => Ok(()),
Err(keyring::Error::NoEntry) => Ok(()), Err(e) => Err(e).context("Failed to delete API key from keyring"),
}
}
pub fn is_available() -> bool {
match keyring::Entry::new(SERVICE_NAME, "__test__") {
Ok(entry) => {
match entry.get_password() {
Err(keyring::Error::NoStorageAccess(_)) => false,
_ => true, }
}
Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_PROFILE: &str = "linear-cli-test-profile";
const TEST_KEY: &str = "lin_api_test_key_12345";
#[test]
fn test_is_available() {
let available = is_available();
println!("Keyring available: {}", available);
}
#[test]
fn test_set_get_delete_key() {
if !is_available() {
eprintln!("Skipping keyring test - keyring not available");
return;
}
let _ = delete_key(TEST_PROFILE);
if let Err(e) = set_key(TEST_PROFILE, TEST_KEY) {
eprintln!("Skipping test - set_key failed: {}", e);
return;
}
match get_key(TEST_PROFILE) {
Ok(Some(key)) => {
assert_eq!(key, TEST_KEY, "Key should match");
}
Ok(None) => {
eprintln!("Warning: Key not found after set - keyring may not be persistent in this environment");
}
Err(e) => {
eprintln!("Warning: get_key failed: {}", e);
}
}
let _ = delete_key(TEST_PROFILE);
}
#[test]
fn test_delete_nonexistent_key() {
if !is_available() {
eprintln!("Skipping keyring test - keyring not available");
return;
}
let result = delete_key("nonexistent-profile-xyz");
assert!(result.is_ok(), "Deleting nonexistent key should succeed");
}
#[test]
fn test_overwrite_key() {
if !is_available() {
eprintln!("Skipping keyring test - keyring not available");
return;
}
let profile = "linear-cli-test-overwrite";
let _ = delete_key(profile);
if let Err(e) = set_key(profile, "key1") {
eprintln!("Skipping test - set_key failed: {}", e);
return;
}
match get_key(profile) {
Ok(Some(key)) if key == "key1" => {
set_key(profile, "key2").expect("Failed to set key2");
if let Ok(Some(key2)) = get_key(profile) {
assert_eq!(key2, "key2", "Overwritten key should match");
}
}
_ => {
eprintln!("Warning: Keyring not persistent in this environment");
}
}
let _ = delete_key(profile);
}
}