mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-11 01:51:34 -10:00
test(tui): cover keyless recovery with a real Engine error
Submit a keyless route to Engine::new, project the typed authentication error through the real UI handler, then project a real config acknowledgement and render at 80x24 and 140x40. A loopback server guards against provider I/O and must receive zero requests. This replaces the old WIP premise that Engine construction returns a missing-key error. Validation: cargo fmt --all -- --check and git diff --check pass. Rust tests were not run locally; hosted CI must qualify this head. Refs #6455 Signed-off-by: CodeWhale Bot <[email protected]>
This commit is contained in:
1 parent
25e32b05ce
commit
795906a3d8
1 file changed
+141
@@ -25229,6 +25229,147 @@ fn non_recoverable_engine_error_enters_offline_mode() {
|
||||
assert!(app.pending_provider_switch.is_none());
|
||||
}
|
||||
|
||||
/// U1: use the real Engine's typed auth failure, not the old WIP assumption
|
||||
/// that Engine construction fails synchronously. A routine acknowledgement
|
||||
/// may replace the footer; the recovery must still be readable in the frame.
|
||||
#[tokio::test]
|
||||
async fn keyless_engine_error_stays_visible_after_a_config_ack() {
|
||||
use crate::core::engine::{Engine, EngineConfig};
|
||||
use crate::core::ops::{Op, TurnSpec, UserInputProvenance};
|
||||
use crate::error_taxonomy::ErrorCategory;
|
||||
|
||||
let _home = SettingsHomeGuard::new();
|
||||
let _key = crate::test_support::EnvVarGuard::remove("DEEPSEEK_API_KEY");
|
||||
let workspace = TempDir::new().expect("workspace");
|
||||
// Even an accidental credential fallback can only reach this local
|
||||
// server. Requiring zero requests proves rejection before provider I/O.
|
||||
let server = wiremock::MockServer::start().await;
|
||||
let config = Config {
|
||||
provider: Some("deepseek".to_string()),
|
||||
api_key: Some(String::new()),
|
||||
providers: Some(ProvidersConfig {
|
||||
deepseek: ProviderConfig {
|
||||
base_url: Some(format!("{}/v1", server.uri())),
|
||||
auth_mode: Some("api_key".to_string()),
|
||||
..ProviderConfig::default()
|
||||
},
|
||||
..ProvidersConfig::default()
|
||||
}),
|
||||
..Config::default()
|
||||
};
|
||||
let mut app = create_test_app();
|
||||
app.workspace = workspace.path().to_path_buf();
|
||||
app.onboarding = OnboardingState::None;
|
||||
app.launch.visible = false;
|
||||
app.api_key_env_only = false;
|
||||
app.is_loading = true;
|
||||
app.runtime_turn_status = Some("in_progress".to_string());
|
||||
app.add_message(HistoryCell::User {
|
||||
content: "hello without a key".to_string(),
|
||||
});
|
||||
let (engine, handle) = Engine::new(
|
||||
EngineConfig {
|
||||
workspace: workspace.path().to_path_buf(),
|
||||
snapshots_enabled: false,
|
||||
subagents_enabled: false,
|
||||
terminal_chrome_enabled: false,
|
||||
..EngineConfig::default()
|
||||
},
|
||||
&config,
|
||||
);
|
||||
let run = tokio::spawn(engine.run());
|
||||
handle
|
||||
.send(Op::SendMessage(TurnSpec {
|
||||
content: "hello without a key".to_string(),
|
||||
images: Vec::new(),
|
||||
mode: AppMode::Agent,
|
||||
route: Box::new(
|
||||
resolve_runtime_route(&config, ApiProvider::Deepseek, Some(&app.model))
|
||||
.expect("structural route resolution"),
|
||||
),
|
||||
compaction: Box::default(),
|
||||
initial_routed_usage: Box::default(),
|
||||
max_output_tokens: None,
|
||||
goal_objective: None,
|
||||
goal_token_budget: None,
|
||||
goal_status: crate::tools::goal::GoalStatus::Active,
|
||||
reasoning_effort: None,
|
||||
reasoning_effort_auto: false,
|
||||
auto_model: false,
|
||||
allow_shell: false,
|
||||
trust_mode: false,
|
||||
auto_approve: false,
|
||||
approval_mode: ApprovalMode::Suggest,
|
||||
translation_enabled: false,
|
||||
allowed_tools: None,
|
||||
dynamic_tools: Vec::new(),
|
||||
hook_executor: None,
|
||||
verbosity: None,
|
||||
provenance: UserInputProvenance::ExternalUser,
|
||||
}))
|
||||
.await
|
||||
.expect("submit to the real Engine");
|
||||
|
||||
let mut events = handle.rx_event.write().await;
|
||||
loop {
|
||||
let event = tokio::time::timeout(Duration::from_secs(10), events.recv())
|
||||
.await
|
||||
.expect("keyless Engine response")
|
||||
.expect("Engine event");
|
||||
if let EngineEvent::Error { envelope, .. } = event {
|
||||
assert_eq!(envelope.category, ErrorCategory::Authentication);
|
||||
assert!(!envelope.recoverable);
|
||||
assert!(envelope.message.contains("API key not found"));
|
||||
apply_engine_error_to_app(&mut app, envelope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut compaction = crate::compaction::CompactionConfig::default();
|
||||
compaction.enabled = !compaction.enabled;
|
||||
handle
|
||||
.send(Op::SetCompaction { config: compaction })
|
||||
.await
|
||||
.expect("change config after the failed turn");
|
||||
loop {
|
||||
let event = tokio::time::timeout(Duration::from_secs(10), events.recv())
|
||||
.await
|
||||
.expect("config acknowledgement")
|
||||
.expect("Engine event");
|
||||
if let EngineEvent::Status { message } = event {
|
||||
// Same projection as the event loop's Status arm.
|
||||
app.status_message = Some(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
drop(events);
|
||||
assert!(app.offline_mode);
|
||||
assert!(!app.is_loading);
|
||||
for (width, height) in [(80, 24), (140, 40)] {
|
||||
let frame = render_test_app(&mut app, &config, width, height);
|
||||
let text = frame.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
assert!(
|
||||
text.contains("API key not found"),
|
||||
"{width}x{height}: {text}"
|
||||
);
|
||||
assert!(
|
||||
text.contains("codewhale auth set"),
|
||||
"{width}x{height}: {text}"
|
||||
);
|
||||
}
|
||||
assert!(
|
||||
server
|
||||
.received_requests()
|
||||
.await
|
||||
.expect("requests")
|
||||
.is_empty()
|
||||
);
|
||||
handle.send(Op::Shutdown).await.expect("shutdown");
|
||||
tokio::time::timeout(Duration::from_secs(10), run)
|
||||
.await
|
||||
.expect("Engine shutdown")
|
||||
.expect("Engine task");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn env_only_auth_failure_reopens_provider_onboarding() {
|
||||
use crate::error_taxonomy::ErrorEnvelope;
|
||||
|
||||
Reference in new issue
Block a user