From 2cb06f672c5a83e3d77d8066da1dc5a357a9c167 Mon Sep 17 00:00:00 2001 From: Greyforge Admin Date: Wed, 20 May 2026 00:11:17 -0400 Subject: [PATCH] Handle logout confirmation read errors --- src/cortex-cli/src/login.rs | 23 ++++++++--- src/cortex-cli/tests/logout_confirmation.rs | 44 +++++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 src/cortex-cli/tests/logout_confirmation.rs diff --git a/src/cortex-cli/src/login.rs b/src/cortex-cli/src/login.rs index 138451fc9..c3499247f 100644 --- a/src/cortex-cli/src/login.rs +++ b/src/cortex-cli/src/login.rs @@ -7,7 +7,7 @@ use cortex_login::{ safe_format_key, save_auth_with_fallback, }; use std::collections::HashSet; -use std::io::{IsTerminal, Read}; +use std::io::{BufRead, IsTerminal, Read}; use std::path::PathBuf; /// Check for duplicate config override keys and warn the user. @@ -41,6 +41,14 @@ fn get_cortex_home() -> PathBuf { }) } +/// Read and parse a logout confirmation response. +pub fn read_logout_confirmation(reader: &mut R) -> std::io::Result { + let mut input = String::new(); + reader.read_line(&mut input)?; + let input = input.trim().to_lowercase(); + Ok(input == "y" || input == "yes") +} + /// Run login with API key. pub async fn run_login_with_api_key(config_overrides: CliConfigOverrides, api_key: String) -> ! { check_duplicate_config_overrides(&config_overrides); @@ -205,13 +213,18 @@ pub async fn run_logout(config_overrides: CliConfigOverrides, skip_confirmation: ); let _ = std::io::Write::flush(&mut std::io::stderr()); - let mut input = String::new(); - if std::io::stdin().read_line(&mut input).is_ok() { - let input = input.trim().to_lowercase(); - if input != "y" && input != "yes" { + let stdin = std::io::stdin(); + let mut stdin = stdin.lock(); + match read_logout_confirmation(&mut stdin) { + Ok(true) => {} + Ok(false) => { print_info("Logout cancelled."); std::process::exit(0); } + Err(e) => { + print_error(&format!("Failed to read logout confirmation: {e}")); + std::process::exit(1); + } } } } diff --git a/src/cortex-cli/tests/logout_confirmation.rs b/src/cortex-cli/tests/logout_confirmation.rs new file mode 100644 index 000000000..fe15c2455 --- /dev/null +++ b/src/cortex-cli/tests/logout_confirmation.rs @@ -0,0 +1,44 @@ +use std::io::{self, BufRead, Cursor, Read}; + +use cortex_cli::login::read_logout_confirmation; + +struct FailingReader; + +impl Read for FailingReader { + fn read(&mut self, _buf: &mut [u8]) -> io::Result { + Err(io::Error::other("stdin unavailable")) + } +} + +impl BufRead for FailingReader { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + Err(io::Error::other("stdin unavailable")) + } + + fn consume(&mut self, _amt: usize) {} +} + +#[test] +fn logout_confirmation_accepts_yes_values() { + let mut lowercase = Cursor::new(b"yes\n"); + assert!(read_logout_confirmation(&mut lowercase).unwrap()); + + let mut uppercase = Cursor::new(b"Y\n"); + assert!(read_logout_confirmation(&mut uppercase).unwrap()); +} + +#[test] +fn logout_confirmation_rejects_empty_or_negative_values() { + let mut empty = Cursor::new(b"\n"); + assert!(!read_logout_confirmation(&mut empty).unwrap()); + + let mut no = Cursor::new(b"no\n"); + assert!(!read_logout_confirmation(&mut no).unwrap()); +} + +#[test] +fn logout_confirmation_propagates_read_errors() { + let mut reader = FailingReader; + let error = read_logout_confirmation(&mut reader).unwrap_err(); + assert_eq!(error.kind(), io::ErrorKind::Other); +}