#![cfg(unix)]
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use embacle::config::RunnerConfig;
use embacle::types::{ChatMessage, ChatRequest, ErrorKind, LlmProvider};
use embacle::ClaudeCodeRunner;
fn fake_claude(dir: &Path, stdout: &str, exit_code: i32) -> PathBuf {
let path = dir.join("claude");
let script = format!("#!/bin/sh\ncat <<'CLAUDE_EOF'\n{stdout}\nCLAUDE_EOF\nexit {exit_code}\n");
fs::write(&path, script).unwrap();
fs::set_permissions(&path, fs::Permissions::from_mode(0o755)).unwrap();
path
}
fn ping() -> ChatRequest {
ChatRequest::new(vec![ChatMessage::user("ping")])
}
#[tokio::test]
async fn an_error_document_outranks_the_exit_code() {
let dir = tempfile::tempdir().unwrap();
let padding = "x".repeat(600);
let doc = format!(
r#"{{"duration_api_ms":6681,"session_id":"{padding}","usage":{{"input_tokens":8,"output_tokens":4}},"is_error":true,"subtype":"error_during_execution","api_error_status":429,"result":"Claude usage limit reached"}}"#
);
let runner = ClaudeCodeRunner::new(RunnerConfig::new(fake_claude(dir.path(), &doc, 1)));
let err = runner.complete(&ping()).await.unwrap_err();
assert_eq!(err.kind, ErrorKind::RateLimit, "{}", err.message);
assert!(
err.message.contains("usage limit reached"),
"{}",
err.message
);
assert!(
err.message.contains("api_error_status: 429"),
"{}",
err.message
);
assert!(!err.message.contains("duration_api_ms"), "{}", err.message);
}
#[tokio::test]
async fn a_non_document_on_a_failed_exit_keeps_the_exit_diagnostic() {
let dir = tempfile::tempdir().unwrap();
let binary = fake_claude(dir.path(), "Not logged in · Please run /login", 1);
let runner = ClaudeCodeRunner::new(RunnerConfig::new(binary));
let err = runner.complete(&ping()).await.unwrap_err();
assert_eq!(err.kind, ErrorKind::ExternalService);
assert!(
err.message.contains("exited with code 1"),
"{}",
err.message
);
assert!(err.message.contains("Not logged in"), "{}", err.message);
}
#[tokio::test]
async fn a_success_document_on_a_failed_exit_is_still_a_failure() {
let dir = tempfile::tempdir().unwrap();
let binary = fake_claude(dir.path(), r#"{"is_error":false,"result":"pong"}"#, 3);
let runner = ClaudeCodeRunner::new(RunnerConfig::new(binary));
let err = runner.complete(&ping()).await.unwrap_err();
assert!(
err.message.contains("exited with code 3"),
"{}",
err.message
);
}