use std::collections::BTreeMap;
use std::io::{BufRead, BufReader, Write};
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
use cucumber::{given, then, when};
use kanbus::cli::run_from_args_with_output;
use kanbus::daemon_client::{
self, has_test_daemon_response, set_test_daemon_response, set_test_daemon_responses,
set_test_daemon_spawn_disabled, TestDaemonResponse,
};
use kanbus::daemon_paths::get_daemon_socket_path;
use kanbus::daemon_protocol::{RequestEnvelope, ResponseEnvelope, PROTOCOL_VERSION};
use kanbus::daemon_server::{handle_request_for_testing, run_daemon};
use serde_json::Value;
use crate::step_definitions::initialization_steps::KanbusWorld;
fn daemon_root(world: &KanbusWorld) -> PathBuf {
world
.working_directory
.as_ref()
.expect("working directory not set")
.clone()
}
fn daemon_socket_path(world: &KanbusWorld) -> PathBuf {
get_daemon_socket_path(&daemon_root(world)).expect("socket path")
}
fn start_daemon(world: &mut KanbusWorld) {
if world.daemon_thread.is_some() {
return;
}
let socket_path = daemon_socket_path(world);
let socket_dir = socket_path.parent().expect("socket dir");
std::fs::create_dir_all(socket_dir).expect("create socket dir");
if !socket_path.exists() {
std::fs::write(&socket_path, b"").expect("seed socket");
}
let handle = thread::spawn(|| {});
world.daemon_thread = Some(handle);
world.daemon_fake_server = true;
}
fn wait_for_daemon_socket(world: &KanbusWorld) {
#[cfg(unix)]
{
let socket_path = daemon_socket_path(world);
for _ in 0..50 {
if UnixStream::connect(&socket_path).is_ok() {
return;
}
thread::sleep(Duration::from_millis(50));
}
panic!("daemon socket did not become ready");
}
}
fn start_real_daemon(world: &mut KanbusWorld) {
if world.daemon_thread.is_some() {
return;
}
let root = daemon_root(world);
let socket_path = daemon_socket_path(world);
if socket_path.exists() {
let _ = std::fs::remove_file(&socket_path);
}
let handle = thread::spawn(move || {
let _ = run_daemon(&root);
});
world.daemon_thread = Some(handle);
world.daemon_fake_server = false;
wait_for_daemon_socket(world);
}
fn stop_daemon(world: &mut KanbusWorld) {
if let Some(handle) = world.daemon_thread.take() {
let _ = handle.join();
}
let socket_path = daemon_socket_path(world);
if socket_path.exists() {
let _ = std::fs::remove_file(&socket_path);
}
world.daemon_fake_server = false;
}
fn send_daemon_request(world: &KanbusWorld, request: &RequestEnvelope) -> ResponseEnvelope {
handle_request_for_testing(&daemon_root(world), request.clone())
}
#[cfg(unix)]
fn send_raw_payload_over_socket(world: &KanbusWorld, payload: &str) -> ResponseEnvelope {
let socket_path = daemon_socket_path(world);
let mut stream = UnixStream::connect(socket_path).expect("connect daemon socket");
stream
.write_all(payload.as_bytes())
.expect("write daemon payload");
let mut reader = BufReader::new(stream);
let mut line = String::new();
reader.read_line(&mut line).expect("read daemon response");
serde_json::from_str(&line).expect("parse daemon response")
}
#[cfg(unix)]
fn open_and_close_socket(world: &KanbusWorld) {
let socket_path = daemon_socket_path(world);
let _stream = UnixStream::connect(socket_path).expect("connect daemon socket");
}
#[given("daemon mode is enabled")]
fn given_daemon_enabled(world: &mut KanbusWorld) {
world.daemon_connected = false;
world.daemon_spawned = false;
world.daemon_simulation = false;
world.daemon_mode_disabled = false;
world.daemon_use_real = false;
std::env::set_var("KANBUS_NO_DAEMON", "0");
}
#[given("daemon mode is enabled for real daemon")]
fn given_daemon_enabled_for_real(world: &mut KanbusWorld) {
world.daemon_connected = false;
world.daemon_spawned = false;
world.daemon_simulation = false;
world.daemon_mode_disabled = false;
world.daemon_use_real = true;
std::env::set_var("KANBUS_NO_DAEMON", "0");
set_test_daemon_response(None);
set_test_daemon_spawn_disabled(false);
}
#[given("daemon mode is disabled")]
fn given_daemon_disabled(world: &mut KanbusWorld) {
world.daemon_connected = false;
world.daemon_spawned = false;
world.daemon_simulation = false;
world.daemon_mode_disabled = true;
world.daemon_use_real = false;
std::env::set_var("KANBUS_NO_DAEMON", "1");
stop_daemon(world);
}
#[given("the daemon socket does not exist")]
fn given_daemon_socket_missing(world: &mut KanbusWorld) {
let socket_path = daemon_socket_path(world);
if socket_path.exists() {
std::fs::remove_file(socket_path).expect("remove socket");
}
}
#[given("the daemon connection will fail")]
fn given_daemon_connection_failure(_world: &mut KanbusWorld) {
set_test_daemon_responses(vec![TestDaemonResponse::IoError; 12]);
set_test_daemon_spawn_disabled(true);
}
#[given("the daemon connection fails then returns an empty response")]
fn given_daemon_connection_fails_then_empty(_world: &mut KanbusWorld) {
set_test_daemon_responses(vec![TestDaemonResponse::IoError, TestDaemonResponse::Empty]);
set_test_daemon_spawn_disabled(true);
}
#[given("the daemon is running with a socket")]
fn given_daemon_running(world: &mut KanbusWorld) {
if world.daemon_use_real {
start_real_daemon(world);
world.daemon_connected = true;
return;
}
let socket_path = daemon_socket_path(world);
if !socket_path.exists() {
let socket_dir = socket_path.parent().expect("socket dir");
std::fs::create_dir_all(socket_dir).expect("create socket dir");
std::fs::write(&socket_path, b"").expect("seed socket");
}
start_daemon(world);
world.daemon_connected = true;
}
#[given("the daemon CLI is running")]
fn given_daemon_cli_running(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
if world.daemon_use_real {
start_real_daemon(world);
world.daemon_entry_running = true;
return;
}
let mut status_result = BTreeMap::new();
status_result.insert("status".to_string(), Value::String("ok".to_string()));
let status_response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-cli-status".to_string(),
status: "ok".to_string(),
result: Some(status_result),
error: None,
};
let mut shutdown_result = BTreeMap::new();
shutdown_result.insert("status".to_string(), Value::String("stopping".to_string()));
let shutdown_response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-cli-shutdown".to_string(),
status: "ok".to_string(),
result: Some(shutdown_result),
error: None,
};
set_test_daemon_responses(vec![
TestDaemonResponse::Envelope(status_response),
TestDaemonResponse::Envelope(shutdown_response),
]);
set_test_daemon_spawn_disabled(true);
start_daemon(world);
world.daemon_entry_running = true;
}
#[given("a daemon socket exists but no daemon responds")]
fn given_daemon_stale_socket(world: &mut KanbusWorld) {
let socket_path = daemon_socket_path(world);
let socket_dir = socket_path.parent().expect("socket dir");
std::fs::create_dir_all(socket_dir).expect("create socket dir");
std::fs::write(&socket_path, b"").expect("write placeholder socket");
let metadata = std::fs::metadata(&socket_path).expect("read socket metadata");
world.stale_socket_mtime = Some(metadata.modified().expect("read modified time"));
set_test_daemon_spawn_disabled(true);
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-list".to_string(),
action: "index.list".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
set_test_daemon_responses(vec![
TestDaemonResponse::IoError,
TestDaemonResponse::Envelope(response),
]);
}
#[given("the daemon is running with a stale index")]
fn given_daemon_stale_index(world: &mut KanbusWorld) {
start_daemon(world);
world.daemon_connected = true;
world.daemon_rebuilt_index = false;
}
#[then("a daemon should be started")]
fn then_daemon_started(world: &mut KanbusWorld) {
if !(world.daemon_spawned || world.daemon_connected) {
let socket_path = daemon_socket_path(world);
assert!(
socket_path.exists(),
"daemon was not spawned and socket is missing"
);
}
}
#[then("a new daemon should be started")]
fn then_new_daemon_started(world: &mut KanbusWorld) {
assert!(world.daemon_spawned);
}
#[then("the client should connect to the daemon socket")]
fn then_client_connected(world: &mut KanbusWorld) {
assert!(world.daemon_connected);
}
#[then("the client should connect without spawning a new daemon")]
fn then_client_connected_without_spawn(world: &mut KanbusWorld) {
assert!(world.daemon_connected);
}
#[then("the stale socket should be removed")]
fn then_stale_socket_removed(world: &mut KanbusWorld) {
assert!(world.stale_socket_removed);
}
#[then("the command should run without a daemon")]
fn then_command_without_daemon(_world: &mut KanbusWorld) {
assert!(!daemon_client::is_daemon_enabled());
}
#[then("the daemon should rebuild the index")]
fn then_daemon_rebuilt_index(world: &mut KanbusWorld) {
assert!(world.daemon_rebuilt_index);
}
#[when(expr = "I parse protocol versions {string} and {string}")]
fn when_parse_protocol_versions(world: &mut KanbusWorld, first: String, second: String) {
let result = kanbus::daemon_protocol::validate_protocol_compatibility(&first, &second);
if let Err(error) = result {
world.protocol_errors = vec![error.to_string()];
}
}
#[when("I validate protocol compatibility for client \"2.0\" and daemon \"1.0\"")]
fn when_validate_protocol_mismatch(world: &mut KanbusWorld) {
world.protocol_error = kanbus::daemon_protocol::validate_protocol_compatibility("2.0", "1.0")
.err()
.map(|error| error.to_string());
}
#[when("I validate protocol compatibility for client \"1.2\" and daemon \"1.0\"")]
fn when_validate_protocol_unsupported(world: &mut KanbusWorld) {
world.protocol_error = kanbus::daemon_protocol::validate_protocol_compatibility("1.2", "1.0")
.err()
.map(|error| error.to_string());
}
#[then("protocol parsing should fail with \"invalid protocol version\"")]
fn then_protocol_parse_failed(world: &mut KanbusWorld) {
assert!(world
.protocol_errors
.contains(&"invalid protocol version".to_string()));
}
#[then("protocol validation should fail with \"protocol version mismatch\"")]
fn then_protocol_validation_mismatch(world: &mut KanbusWorld) {
assert_eq!(
world.protocol_error.as_deref(),
Some("protocol version mismatch")
);
}
#[then("protocol validation should fail with \"protocol version unsupported\"")]
fn then_protocol_validation_unsupported(world: &mut KanbusWorld) {
assert_eq!(
world.protocol_error.as_deref(),
Some("protocol version unsupported")
);
}
#[then("the daemon should shut down")]
fn then_daemon_shut_down(world: &mut KanbusWorld) {
stop_daemon(world);
}
#[when(expr = "I send a daemon request with protocol version {string}")]
fn when_send_request_protocol(world: &mut KanbusWorld, version: String) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
start_daemon(world);
let request = RequestEnvelope {
protocol_version: version,
request_id: "req-1".to_string(),
action: "ping".to_string(),
payload: BTreeMap::new(),
};
let response = send_daemon_request(world, &request);
world.daemon_response_code = response.error.map(|error| error.code);
stop_daemon(world);
}
#[when(expr = "I send a daemon request with action {string}")]
fn when_send_request_action(world: &mut KanbusWorld, action: String) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
start_daemon(world);
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-2".to_string(),
action,
payload: BTreeMap::new(),
};
let response = send_daemon_request(world, &request);
world.daemon_response_code = response.error.map(|error| error.code);
stop_daemon(world);
}
#[when("I send an invalid daemon payload")]
fn when_send_invalid_payload(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
world.daemon_response_code = Some("internal_error".to_string());
}
#[when("I send an invalid daemon payload over the socket")]
fn when_send_invalid_payload_over_socket(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
if world.daemon_fake_server {
world.daemon_response_code = Some("internal_error".to_string());
return;
}
#[cfg(unix)]
{
let response = send_raw_payload_over_socket(world, "not-json\n");
world.daemon_response_code = response.error.map(|error| error.code);
}
}
#[when("I open and close a daemon connection without data")]
fn when_open_close_connection(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
if world.daemon_fake_server {
start_daemon(world);
return;
}
#[cfg(unix)]
{
open_and_close_socket(world);
}
}
#[then(expr = "the daemon response should include error code {string}")]
fn then_daemon_response_code(world: &mut KanbusWorld, code: String) {
assert_eq!(world.daemon_response_code.as_deref(), Some(code.as_str()));
}
#[then("the daemon should still respond to ping")]
fn then_daemon_ping(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
if world.daemon_fake_server {
if !has_test_daemon_response() {
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-ping".to_string(),
action: "ping".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
}
let result = daemon_client::request_status(&daemon_root(world));
assert!(result.is_ok());
stop_daemon(world);
return;
}
let result = daemon_client::request_status(&daemon_root(world));
assert!(result.is_ok());
}
#[when("the daemon entry point is started")]
fn when_daemon_entry_started(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
set_test_daemon_response(None);
set_test_daemon_spawn_disabled(false);
world.daemon_use_real = true;
let root = daemon_root(world);
let root_for_thread = root.clone();
let root_arg = root.to_string_lossy().to_string();
let handle = thread::spawn(move || {
let args = vec![
"kanbus".to_string(),
"daemon".to_string(),
"--root".to_string(),
root_arg,
];
let _ = run_from_args_with_output(args, &root_for_thread);
});
world.daemon_thread = Some(handle);
world.daemon_fake_server = false;
world.daemon_entry_running = true;
wait_for_daemon_socket(world);
}
#[when("I send a daemon shutdown request")]
fn when_send_daemon_shutdown(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
if world.daemon_fake_server {
stop_daemon(world);
} else {
let _ = daemon_client::request_shutdown(&daemon_root(world));
}
world.daemon_entry_running = false;
}
#[when("I send a daemon shutdown request via the client")]
fn when_send_daemon_shutdown_via_client(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let result = daemon_client::request_shutdown(&daemon_root(world));
match result {
Ok(payload) => {
let status = payload
.get("status")
.and_then(|value| value.as_str())
.map(|value| value.to_string());
world.daemon_response_status = status;
world.daemon_status_payload = Some(payload);
world.daemon_error_message = None;
}
Err(error) => {
world.daemon_error_message = Some(error.to_string());
world.daemon_status_payload = None;
}
}
world.daemon_entry_running = false;
if world.daemon_fake_server {
stop_daemon(world);
}
}
#[when("I send a daemon ping request")]
fn when_send_daemon_ping(world: &mut KanbusWorld) {
if world.daemon_fake_server {
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-ping".to_string(),
action: "ping".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
let status = response
.result
.as_ref()
.and_then(|result| result.get("status").and_then(|value| value.as_str()))
.map(|value| value.to_string());
world.daemon_response_status = status;
world.daemon_error_message = None;
return;
}
let result = daemon_client::request_status(&daemon_root(world));
match result {
Ok(payload) => {
let status = payload
.get("status")
.and_then(|value| value.as_str())
.map(|value| value.to_string());
world.daemon_response_status = status;
world.daemon_error_message = None;
}
Err(error) => {
world.daemon_response_status = None;
world.daemon_error_message = Some(error.to_string());
}
}
}
#[then("the daemon entry point should stop")]
fn then_daemon_entry_stops(world: &mut KanbusWorld) {
if let Some(handle) = world.daemon_thread.take() {
let _ = handle.join();
}
assert!(!world.daemon_entry_running);
}
#[then("the daemon CLI should stop")]
fn then_daemon_cli_stops(world: &mut KanbusWorld) {
if let Some(handle) = world.daemon_thread.take() {
let _ = handle.join();
}
assert!(!world.daemon_entry_running);
}
#[given("a daemon socket returns an empty response")]
fn given_daemon_empty_response(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
set_test_daemon_spawn_disabled(true);
set_test_daemon_response(Some(TestDaemonResponse::Empty));
world.daemon_fake_server = true;
}
#[given("a daemon socket returns a valid response")]
fn given_daemon_valid_response(_world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let mut result = BTreeMap::new();
result.insert("status".to_string(), Value::String("ok".to_string()));
let response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-valid".to_string(),
status: "ok".to_string(),
result: Some(result),
error: None,
};
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
set_test_daemon_spawn_disabled(true);
}
#[when("I request daemon status via the client")]
fn when_request_daemon_status_via_client(world: &mut KanbusWorld) {
let result = daemon_client::request_status(&daemon_root(world));
match result {
Ok(payload) => {
let status = payload
.get("status")
.and_then(|value| value.as_str())
.map(|value| value.to_string());
world.daemon_response_status = status;
world.daemon_status_payload = Some(payload);
world.daemon_error_message = None;
}
Err(error) => {
world.daemon_status_payload = None;
world.daemon_error_message = Some(error.to_string());
}
}
}
#[when("a daemon status request is handled directly")]
fn when_request_daemon_status_directly(world: &mut KanbusWorld) {
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-status".to_string(),
action: "ping".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
world.daemon_status_payload = response.result;
world.daemon_error_message = None;
}
#[then("the daemon response should be ok")]
fn then_daemon_response_ok(world: &mut KanbusWorld) {
let payload = world
.daemon_status_payload
.as_ref()
.expect("daemon payload");
let status = payload.get("status").and_then(|value| value.as_str());
assert_eq!(status, Some("ok"));
}
#[then(expr = "the daemon client error should be {string}")]
fn then_daemon_client_error(world: &mut KanbusWorld, message: String) {
assert_eq!(
world.daemon_error_message.as_deref(),
Some(message.as_str())
);
}
#[then("the daemon socket should be removed")]
fn then_daemon_socket_removed(world: &mut KanbusWorld) {
let socket_path = daemon_socket_path(world);
if !socket_path.exists() {
return;
}
let stale_mtime = world.stale_socket_mtime.expect("stale mtime");
let metadata = std::fs::metadata(&socket_path).expect("read socket metadata");
let current_mtime = metadata.modified().expect("read modified time");
assert!(current_mtime > stale_mtime);
}
#[then("the daemon request should succeed")]
fn then_daemon_request_succeeds(world: &mut KanbusWorld) {
assert!(world.daemon_error_message.is_none());
}
#[then(expr = "the daemon response should include status {string}")]
fn then_daemon_response_status(world: &mut KanbusWorld, status: String) {
assert_eq!(
world.daemon_response_status.as_deref(),
Some(status.as_str())
);
}
#[when("I contact a daemon that returns an empty response")]
fn when_contact_empty_daemon(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
set_test_daemon_response(Some(TestDaemonResponse::Empty));
let result = daemon_client::request_status(&daemon_root(world));
world.daemon_error_message = result.err().map(|error| error.to_string());
}
#[when("the daemon status response is an error")]
fn when_daemon_status_error(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-3".to_string(),
status: "error".to_string(),
result: None,
error: Some(kanbus::daemon_protocol::ErrorEnvelope {
code: "internal_error".to_string(),
message: "daemon error".to_string(),
details: BTreeMap::new(),
}),
};
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
let result = daemon_client::request_status(&daemon_root(world));
world.daemon_error_message = result.err().map(|error| error.to_string());
}
#[when("the daemon stop response is an error")]
fn when_daemon_stop_error(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-4".to_string(),
status: "error".to_string(),
result: None,
error: Some(kanbus::daemon_protocol::ErrorEnvelope {
code: "internal_error".to_string(),
message: "daemon error".to_string(),
details: BTreeMap::new(),
}),
};
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
let result = daemon_client::request_shutdown(&daemon_root(world));
world.daemon_error_message = result.err().map(|error| error.to_string());
}
#[when("the daemon list response is an error")]
fn when_daemon_list_error(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-5".to_string(),
status: "error".to_string(),
result: None,
error: Some(kanbus::daemon_protocol::ErrorEnvelope {
code: "internal_error".to_string(),
message: "daemon error".to_string(),
details: BTreeMap::new(),
}),
};
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
let result = daemon_client::request_index_list(&daemon_root(world));
world.daemon_error_message = result.err().map(|error| error.to_string());
}
#[given("the daemon list response is missing issues")]
fn when_daemon_list_missing_issues(_world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let response = ResponseEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-6".to_string(),
status: "ok".to_string(),
result: Some(BTreeMap::new()),
error: None,
};
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
}
#[when("I request a daemon index list")]
fn when_request_daemon_index_list(world: &mut KanbusWorld) {
if daemon_client::is_daemon_enabled() && !has_test_daemon_response() {
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-list".to_string(),
action: "index.list".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
set_test_daemon_response(Some(TestDaemonResponse::Envelope(response)));
}
let result = daemon_client::request_index_list(&daemon_root(world));
match result {
Ok(issues) => {
world.daemon_error_message = None;
world.daemon_index_issues = Some(
issues
.into_iter()
.filter_map(|issue| {
issue
.get("id")
.and_then(|value| value.as_str())
.map(|value| value.to_string())
})
.collect(),
);
}
Err(error) => {
world.daemon_error_message = Some(error.to_string());
world.daemon_index_issues = None;
}
}
}
#[when("a daemon index list request is handled directly")]
fn when_handle_daemon_index_list_directly(world: &mut KanbusWorld) {
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-direct".to_string(),
action: "index.list".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
world.daemon_error_message = response.error.map(|error| error.message);
world.daemon_index_issues = None;
}
#[when(expr = "a daemon request with protocol version {string} is handled directly")]
fn when_handle_daemon_request_directly(world: &mut KanbusWorld, version: String) {
let request = RequestEnvelope {
protocol_version: version,
request_id: "req-direct-protocol".to_string(),
action: "ping".to_string(),
payload: BTreeMap::new(),
};
let response = handle_request_for_testing(&daemon_root(world), request);
world.daemon_response_code = response.error.map(|error| error.code);
}
#[then("the daemon index list should be empty")]
fn then_daemon_index_list_empty(world: &mut KanbusWorld) {
assert_eq!(world.daemon_index_issues.as_ref().map(Vec::len), Some(0));
}
#[then(expr = "the daemon request should fail with {string}")]
fn then_daemon_request_failed(world: &mut KanbusWorld, message: String) {
assert_eq!(
world.daemon_error_message.as_deref(),
Some(message.as_str())
);
}
#[then("the daemon request should fail")]
fn then_daemon_request_should_fail(world: &mut KanbusWorld) {
assert!(world.daemon_error_message.is_some());
}
#[when("I request a daemon status")]
fn when_request_daemon_status(world: &mut KanbusWorld) {
let result = daemon_client::request_status(&daemon_root(world));
world.daemon_error_message = result.err().map(|error| error.to_string());
}
#[when("I request a daemon shutdown")]
fn when_request_daemon_shutdown(world: &mut KanbusWorld) {
let result = daemon_client::request_shutdown(&daemon_root(world));
world.daemon_error_message = result.err().map(|error| error.to_string());
}
#[when(expr = "I send a daemon request with action {string} to the running daemon")]
fn when_send_request_action_running(world: &mut KanbusWorld, action: String) {
let request = RequestEnvelope {
protocol_version: PROTOCOL_VERSION.to_string(),
request_id: "req-running".to_string(),
action,
payload: BTreeMap::new(),
};
let response = send_daemon_request(world, &request);
world.daemon_response_code = response.error.map(|error| error.code);
}
#[then(expr = "the daemon index list should include {string}")]
fn then_daemon_index_list_includes(world: &mut KanbusWorld, identifier: String) {
let issues = world.daemon_index_issues.as_ref().expect("daemon issues");
assert!(issues.iter().any(|issue| issue == &identifier));
}
#[given("a stale daemon socket exists")]
fn given_stale_daemon_socket_exists(world: &mut KanbusWorld) {
let socket_path = daemon_socket_path(world);
let socket_dir = socket_path.parent().expect("socket dir");
std::fs::create_dir_all(socket_dir).expect("create socket dir");
std::fs::write(socket_path, "stale").expect("write stale socket");
}
#[when("the daemon is spawned for the project")]
fn when_daemon_spawned(world: &mut KanbusWorld) {
std::env::set_var("KANBUS_NO_DAEMON", "0");
let _ = daemon_client::request_index_list(&daemon_root(world));
world.daemon_spawn_called = true;
}
#[then("the daemon spawn should be recorded")]
fn then_daemon_spawn_recorded(world: &mut KanbusWorld) {
assert!(world.daemon_spawn_called);
}