storkit: merge 444_refactor_extract_shared_test_helpers_test_ctx_write_story_file_make_api
This commit is contained in:
@@ -142,11 +142,7 @@ mod tests {
|
|||||||
try_handle_command(&dispatch, &format!("@timmy move {args}"))
|
try_handle_command(&dispatch, &format!("@timmy move {args}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_story_file(root: &std::path::Path, stage: &str, filename: &str, content: &str) {
|
use crate::chat::test_helpers::write_story_file;
|
||||||
let dir = root.join(".storkit/work").join(stage);
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
std::fs::write(dir.join(filename), content).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn move_command_is_registered() {
|
fn move_command_is_registered() {
|
||||||
|
|||||||
@@ -91,11 +91,7 @@ mod tests {
|
|||||||
try_handle_command(&dispatch, &format!("@timmy show {args}"))
|
try_handle_command(&dispatch, &format!("@timmy show {args}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_story_file(root: &std::path::Path, stage: &str, filename: &str, content: &str) {
|
use crate::chat::test_helpers::write_story_file;
|
||||||
let dir = root.join(".storkit/work").join(stage);
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
std::fs::write(dir.join(filename), content).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn show_command_is_registered() {
|
fn show_command_is_registered() {
|
||||||
|
|||||||
@@ -296,11 +296,7 @@ mod tests {
|
|||||||
try_handle_command(&dispatch, &format!("@timmy status {args}"))
|
try_handle_command(&dispatch, &format!("@timmy status {args}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_story_file(root: &Path, stage: &str, filename: &str, content: &str) {
|
use crate::chat::test_helpers::write_story_file;
|
||||||
let dir = root.join(".storkit/work").join(stage);
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
std::fs::write(dir.join(filename), content).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- registration -------------------------------------------------------
|
// -- registration -------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -164,11 +164,7 @@ mod tests {
|
|||||||
try_handle_command(&dispatch, &format!("@timmy unblock {args}"))
|
try_handle_command(&dispatch, &format!("@timmy unblock {args}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_story_file(root: &std::path::Path, stage: &str, filename: &str, content: &str) {
|
use crate::chat::test_helpers::write_story_file;
|
||||||
let dir = root.join(".storkit/work").join(stage);
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
std::fs::write(dir.join(filename), content).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unblock_command_is_registered() {
|
fn unblock_command_is_registered() {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ pub mod commands;
|
|||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test_helpers;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//! Shared test utilities for chat handler tests.
|
||||||
|
//!
|
||||||
|
//! Import with `use crate::chat::test_helpers::write_story_file;`
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Write a work-item file into the standard pipeline directory structure.
|
||||||
|
///
|
||||||
|
/// Creates `.storkit/work/{stage}/{filename}` under `root`, creating any
|
||||||
|
/// missing parent directories.
|
||||||
|
pub(crate) fn write_story_file(root: &Path, stage: &str, filename: &str, content: &str) {
|
||||||
|
let dir = root.join(".storkit/work").join(stage);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
std::fs::write(dir.join(filename), content).unwrap();
|
||||||
|
}
|
||||||
@@ -350,11 +350,7 @@ mod tests {
|
|||||||
|
|
||||||
// -- handle_assign (no running coder) ------------------------------------
|
// -- handle_assign (no running coder) ------------------------------------
|
||||||
|
|
||||||
fn write_story_file(root: &Path, stage: &str, filename: &str, content: &str) {
|
use crate::chat::test_helpers::write_story_file;
|
||||||
let dir = root.join(".storkit/work").join(stage);
|
|
||||||
std::fs::create_dir_all(&dir).unwrap();
|
|
||||||
std::fs::write(dir.join(filename), content).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn handle_assign_returns_not_found_for_unknown_number() {
|
async fn handle_assign_returns_not_found_for_unknown_number() {
|
||||||
|
|||||||
@@ -64,6 +64,13 @@ impl AnthropicApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl From<Arc<AppContext>> for AnthropicApi {
|
||||||
|
fn from(ctx: Arc<AppContext>) -> Self {
|
||||||
|
Self::new(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[OpenApi(tag = "AnthropicTags::Anthropic")]
|
#[OpenApi(tag = "AnthropicTags::Anthropic")]
|
||||||
impl AnthropicApi {
|
impl AnthropicApi {
|
||||||
/// Check whether an Anthropic API key is stored.
|
/// Check whether an Anthropic API key is stored.
|
||||||
@@ -151,25 +158,16 @@ impl AnthropicApi {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::{make_api, test_ctx};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn test_ctx(dir: &TempDir) -> AppContext {
|
|
||||||
AppContext::new_test(dir.path().to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_api(dir: &TempDir) -> AnthropicApi {
|
|
||||||
AnthropicApi::new(Arc::new(test_ctx(dir)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- get_anthropic_api_key (private helper) --
|
// -- get_anthropic_api_key (private helper) --
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn get_api_key_returns_err_when_not_set() {
|
fn get_api_key_returns_err_when_not_set() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
let result = get_anthropic_api_key(&ctx);
|
let result = get_anthropic_api_key(&ctx);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.unwrap_err().contains("not found"));
|
assert!(result.unwrap_err().contains("not found"));
|
||||||
@@ -178,7 +176,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_api_key_returns_err_when_empty() {
|
fn get_api_key_returns_err_when_empty() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
ctx.store.set(KEY_ANTHROPIC_API_KEY, json!(""));
|
ctx.store.set(KEY_ANTHROPIC_API_KEY, json!(""));
|
||||||
let result = get_anthropic_api_key(&ctx);
|
let result = get_anthropic_api_key(&ctx);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@@ -188,7 +186,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_api_key_returns_err_when_not_string() {
|
fn get_api_key_returns_err_when_not_string() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
ctx.store.set(KEY_ANTHROPIC_API_KEY, json!(12345));
|
ctx.store.set(KEY_ANTHROPIC_API_KEY, json!(12345));
|
||||||
let result = get_anthropic_api_key(&ctx);
|
let result = get_anthropic_api_key(&ctx);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
@@ -198,7 +196,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_api_key_returns_key_when_set() {
|
fn get_api_key_returns_key_when_set() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
ctx.store.set(KEY_ANTHROPIC_API_KEY, json!("sk-ant-test123"));
|
ctx.store.set(KEY_ANTHROPIC_API_KEY, json!("sk-ant-test123"));
|
||||||
let result = get_anthropic_api_key(&ctx);
|
let result = get_anthropic_api_key(&ctx);
|
||||||
assert_eq!(result.unwrap(), "sk-ant-test123");
|
assert_eq!(result.unwrap(), "sk-ant-test123");
|
||||||
@@ -209,7 +207,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn key_exists_returns_false_when_not_set() {
|
async fn key_exists_returns_false_when_not_set() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<AnthropicApi>(&dir);
|
||||||
let result = api.get_anthropic_api_key_exists().await.unwrap();
|
let result = api.get_anthropic_api_key_exists().await.unwrap();
|
||||||
assert!(!result.0);
|
assert!(!result.0);
|
||||||
}
|
}
|
||||||
@@ -229,7 +227,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_api_key_returns_true() {
|
async fn set_api_key_returns_true() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<AnthropicApi>(&dir);
|
||||||
let payload = Json(ApiKeyPayload {
|
let payload = Json(ApiKeyPayload {
|
||||||
api_key: "sk-ant-test123".to_string(),
|
api_key: "sk-ant-test123".to_string(),
|
||||||
});
|
});
|
||||||
@@ -256,7 +254,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_models_fails_when_no_key() {
|
async fn list_models_fails_when_no_key() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<AnthropicApi>(&dir);
|
||||||
let result = api.list_anthropic_models().await;
|
let result = api.list_anthropic_models().await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
@@ -288,7 +286,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn new_creates_api_instance() {
|
fn new_creates_api_instance() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let _api = make_api(&dir);
|
let _api = make_api::<AnthropicApi>(&dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+25
-24
@@ -138,18 +138,19 @@ impl IoApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl From<std::sync::Arc<AppContext>> for IoApi {
|
||||||
|
fn from(ctx: std::sync::Arc<AppContext>) -> Self {
|
||||||
|
Self { ctx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::make_api;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn make_api(dir: &TempDir) -> IoApi {
|
|
||||||
IoApi {
|
|
||||||
ctx: Arc::new(AppContext::new_test(dir.path().to_path_buf())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- list_directory_absolute ---
|
// --- list_directory_absolute ---
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -158,7 +159,7 @@ mod tests {
|
|||||||
std::fs::create_dir(dir.path().join("subdir")).unwrap();
|
std::fs::create_dir(dir.path().join("subdir")).unwrap();
|
||||||
std::fs::write(dir.path().join("file.txt"), "content").unwrap();
|
std::fs::write(dir.path().join("file.txt"), "content").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: dir.path().to_string_lossy().to_string(),
|
path: dir.path().to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -176,7 +177,7 @@ mod tests {
|
|||||||
let empty = dir.path().join("empty");
|
let empty = dir.path().join("empty");
|
||||||
std::fs::create_dir(&empty).unwrap();
|
std::fs::create_dir(&empty).unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: empty.to_string_lossy().to_string(),
|
path: empty.to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -187,7 +188,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_directory_absolute_errors_on_nonexistent_path() {
|
async fn list_directory_absolute_errors_on_nonexistent_path() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: dir.path().join("nonexistent").to_string_lossy().to_string(),
|
path: dir.path().join("nonexistent").to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -201,7 +202,7 @@ mod tests {
|
|||||||
let file = dir.path().join("not_a_dir.txt");
|
let file = dir.path().join("not_a_dir.txt");
|
||||||
std::fs::write(&file, "content").unwrap();
|
std::fs::write(&file, "content").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: file.to_string_lossy().to_string(),
|
path: file.to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -216,7 +217,7 @@ mod tests {
|
|||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let new_dir = dir.path().join("new_dir");
|
let new_dir = dir.path().join("new_dir");
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(CreateDirectoryPayload {
|
let payload = Json(CreateDirectoryPayload {
|
||||||
path: new_dir.to_string_lossy().to_string(),
|
path: new_dir.to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -231,7 +232,7 @@ mod tests {
|
|||||||
let existing = dir.path().join("existing");
|
let existing = dir.path().join("existing");
|
||||||
std::fs::create_dir(&existing).unwrap();
|
std::fs::create_dir(&existing).unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(CreateDirectoryPayload {
|
let payload = Json(CreateDirectoryPayload {
|
||||||
path: existing.to_string_lossy().to_string(),
|
path: existing.to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -244,7 +245,7 @@ mod tests {
|
|||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let nested = dir.path().join("a").join("b").join("c");
|
let nested = dir.path().join("a").join("b").join("c");
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(CreateDirectoryPayload {
|
let payload = Json(CreateDirectoryPayload {
|
||||||
path: nested.to_string_lossy().to_string(),
|
path: nested.to_string_lossy().to_string(),
|
||||||
});
|
});
|
||||||
@@ -258,7 +259,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_home_directory_returns_a_path() {
|
async fn get_home_directory_returns_a_path() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let result = api.get_home_directory().await.unwrap();
|
let result = api.get_home_directory().await.unwrap();
|
||||||
let home = &result.0;
|
let home = &result.0;
|
||||||
assert!(!home.is_empty());
|
assert!(!home.is_empty());
|
||||||
@@ -272,7 +273,7 @@ mod tests {
|
|||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
std::fs::write(dir.path().join("hello.txt"), "hello world").unwrap();
|
std::fs::write(dir.path().join("hello.txt"), "hello world").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: "hello.txt".to_string(),
|
path: "hello.txt".to_string(),
|
||||||
});
|
});
|
||||||
@@ -283,7 +284,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn read_file_errors_on_missing_file() {
|
async fn read_file_errors_on_missing_file() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: "nonexistent.txt".to_string(),
|
path: "nonexistent.txt".to_string(),
|
||||||
});
|
});
|
||||||
@@ -296,7 +297,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_file_creates_file() {
|
async fn write_file_creates_file() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(WriteFilePayload {
|
let payload = Json(WriteFilePayload {
|
||||||
path: "output.txt".to_string(),
|
path: "output.txt".to_string(),
|
||||||
content: "written content".to_string(),
|
content: "written content".to_string(),
|
||||||
@@ -312,7 +313,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn write_file_creates_parent_dirs() {
|
async fn write_file_creates_parent_dirs() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(WriteFilePayload {
|
let payload = Json(WriteFilePayload {
|
||||||
path: "sub/dir/file.txt".to_string(),
|
path: "sub/dir/file.txt".to_string(),
|
||||||
content: "nested".to_string(),
|
content: "nested".to_string(),
|
||||||
@@ -334,7 +335,7 @@ mod tests {
|
|||||||
std::fs::write(dir.path().join("src/main.rs"), "fn main() {}").unwrap();
|
std::fs::write(dir.path().join("src/main.rs"), "fn main() {}").unwrap();
|
||||||
std::fs::write(dir.path().join("README.md"), "# readme").unwrap();
|
std::fs::write(dir.path().join("README.md"), "# readme").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let result = api.list_project_files().await.unwrap();
|
let result = api.list_project_files().await.unwrap();
|
||||||
let files = &result.0;
|
let files = &result.0;
|
||||||
|
|
||||||
@@ -348,7 +349,7 @@ mod tests {
|
|||||||
std::fs::create_dir(dir.path().join("subdir")).unwrap();
|
std::fs::create_dir(dir.path().join("subdir")).unwrap();
|
||||||
std::fs::write(dir.path().join("file.txt"), "").unwrap();
|
std::fs::write(dir.path().join("file.txt"), "").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let result = api.list_project_files().await.unwrap();
|
let result = api.list_project_files().await.unwrap();
|
||||||
let files = &result.0;
|
let files = &result.0;
|
||||||
|
|
||||||
@@ -363,7 +364,7 @@ mod tests {
|
|||||||
std::fs::write(dir.path().join("z_last.txt"), "").unwrap();
|
std::fs::write(dir.path().join("z_last.txt"), "").unwrap();
|
||||||
std::fs::write(dir.path().join("a_first.txt"), "").unwrap();
|
std::fs::write(dir.path().join("a_first.txt"), "").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let result = api.list_project_files().await.unwrap();
|
let result = api.list_project_files().await.unwrap();
|
||||||
let files = &result.0;
|
let files = &result.0;
|
||||||
|
|
||||||
@@ -380,7 +381,7 @@ mod tests {
|
|||||||
std::fs::create_dir(dir.path().join("adir")).unwrap();
|
std::fs::create_dir(dir.path().join("adir")).unwrap();
|
||||||
std::fs::write(dir.path().join("bfile.txt"), "").unwrap();
|
std::fs::write(dir.path().join("bfile.txt"), "").unwrap();
|
||||||
|
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: ".".to_string(),
|
path: ".".to_string(),
|
||||||
});
|
});
|
||||||
@@ -394,7 +395,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_directory_errors_on_nonexistent() {
|
async fn list_directory_errors_on_nonexistent() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<IoApi>(&dir);
|
||||||
let payload = Json(FilePathPayload {
|
let payload = Json(FilePathPayload {
|
||||||
path: "nonexistent_dir".to_string(),
|
path: "nonexistent_dir".to_string(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -370,13 +370,9 @@ pub(super) async fn get_worktree_commits(worktree_path: &str, base_branch: &str)
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
use crate::store::StoreOps;
|
use crate::store::StoreOps;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tool_list_agents_empty() {
|
fn tool_list_agents_empty() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -279,11 +279,7 @@ pub(super) fn tool_loc_file(args: &Value, ctx: &AppContext) -> Result<String, St
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tool_get_server_logs_no_args_returns_string() {
|
fn tool_get_server_logs_no_args_returns_string() {
|
||||||
|
|||||||
@@ -304,12 +304,9 @@ pub(super) async fn tool_git_log(args: &Value, ctx: &AppContext) -> Result<Strin
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::context::AppContext;
|
||||||
|
use crate::http::test_helpers::test_ctx;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a temp directory with a git worktree structure and init a repo.
|
/// Create a temp directory with a git worktree structure and init a repo.
|
||||||
fn setup_worktree() -> (tempfile::TempDir, PathBuf, AppContext) {
|
fn setup_worktree() -> (tempfile::TempDir, PathBuf, AppContext) {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -164,11 +164,7 @@ pub(super) fn tool_report_merge_failure(args: &Value, ctx: &AppContext) -> Resul
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_git_repo_in(dir: &std::path::Path) {
|
fn setup_git_repo_in(dir: &std::path::Path) {
|
||||||
std::process::Command::new("git")
|
std::process::Command::new("git")
|
||||||
|
|||||||
@@ -1336,11 +1336,7 @@ async fn handle_tools_call(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn json_rpc_response_serializes_success() {
|
fn json_rpc_response_serializes_success() {
|
||||||
|
|||||||
@@ -194,11 +194,7 @@ pub(super) fn find_free_port(start: u16) -> u16 {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn request_qa_in_tools_list() {
|
fn request_qa_in_tools_list() {
|
||||||
|
|||||||
@@ -331,13 +331,9 @@ pub(super) fn handle_run_command_sse(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── is_dangerous ─────────────────────────────────────────────────
|
// ── is_dangerous ─────────────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -549,11 +549,7 @@ pub(super) fn parse_test_cases(value: Option<&Value>) -> Result<Vec<TestCaseResu
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::test_ctx;
|
||||||
|
|
||||||
fn test_ctx(dir: &std::path::Path) -> AppContext {
|
|
||||||
AppContext::new_test(dir.to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_test_cases_empty() {
|
fn parse_test_cases_empty() {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
pub mod agents;
|
pub mod agents;
|
||||||
pub mod agents_sse;
|
pub mod agents_sse;
|
||||||
pub mod anthropic;
|
pub mod anthropic;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test_helpers;
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
pub mod bot_command;
|
pub mod bot_command;
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
|
|||||||
+13
-12
@@ -50,22 +50,23 @@ impl ModelApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl From<std::sync::Arc<AppContext>> for ModelApi {
|
||||||
|
fn from(ctx: std::sync::Arc<AppContext>) -> Self {
|
||||||
|
Self { ctx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::make_api;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn make_api(dir: &TempDir) -> ModelApi {
|
|
||||||
ModelApi {
|
|
||||||
ctx: Arc::new(AppContext::new_test(dir.path().to_path_buf())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_model_preference_returns_none_when_unset() {
|
async fn get_model_preference_returns_none_when_unset() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ModelApi>(&dir);
|
||||||
let result = api.get_model_preference().await.unwrap();
|
let result = api.get_model_preference().await.unwrap();
|
||||||
assert!(result.0.is_none());
|
assert!(result.0.is_none());
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_model_preference_returns_true() {
|
async fn set_model_preference_returns_true() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ModelApi>(&dir);
|
||||||
let payload = Json(ModelPayload {
|
let payload = Json(ModelPayload {
|
||||||
model: "claude-3-sonnet".to_string(),
|
model: "claude-3-sonnet".to_string(),
|
||||||
});
|
});
|
||||||
@@ -84,7 +85,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_model_preference_returns_value_after_set() {
|
async fn get_model_preference_returns_value_after_set() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ModelApi>(&dir);
|
||||||
|
|
||||||
let payload = Json(ModelPayload {
|
let payload = Json(ModelPayload {
|
||||||
model: "claude-3-sonnet".to_string(),
|
model: "claude-3-sonnet".to_string(),
|
||||||
@@ -98,7 +99,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_model_preference_overwrites_previous_value() {
|
async fn set_model_preference_overwrites_previous_value() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ModelApi>(&dir);
|
||||||
|
|
||||||
api.set_model_preference(Json(ModelPayload {
|
api.set_model_preference(Json(ModelPayload {
|
||||||
model: "model-a".to_string(),
|
model: "model-a".to_string(),
|
||||||
@@ -119,7 +120,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_ollama_models_returns_empty_list_for_unreachable_url() {
|
async fn get_ollama_models_returns_empty_list_for_unreachable_url() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ModelApi>(&dir);
|
||||||
// Port 1 is reserved and should immediately refuse the connection.
|
// Port 1 is reserved and should immediately refuse the connection.
|
||||||
let base_url = Query(Some("http://127.0.0.1:1".to_string()));
|
let base_url = Query(Some("http://127.0.0.1:1".to_string()));
|
||||||
let result = api.get_ollama_models(base_url).await;
|
let result = api.get_ollama_models(base_url).await;
|
||||||
|
|||||||
+18
-17
@@ -73,22 +73,23 @@ impl ProjectApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl From<std::sync::Arc<AppContext>> for ProjectApi {
|
||||||
|
fn from(ctx: std::sync::Arc<AppContext>) -> Self {
|
||||||
|
Self { ctx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::make_api;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn make_api(dir: &TempDir) -> ProjectApi {
|
|
||||||
ProjectApi {
|
|
||||||
ctx: Arc::new(AppContext::new_test(dir.path().to_path_buf())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_current_project_returns_none_when_unset() {
|
async fn get_current_project_returns_none_when_unset() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
// Clear the project root that new_test sets
|
// Clear the project root that new_test sets
|
||||||
api.close_project().await.unwrap();
|
api.close_project().await.unwrap();
|
||||||
let result = api.get_current_project().await.unwrap();
|
let result = api.get_current_project().await.unwrap();
|
||||||
@@ -98,7 +99,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_current_project_returns_path_from_state() {
|
async fn get_current_project_returns_path_from_state() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
let result = api.get_current_project().await.unwrap();
|
let result = api.get_current_project().await.unwrap();
|
||||||
assert_eq!(result.0, Some(dir.path().to_string_lossy().to_string()));
|
assert_eq!(result.0, Some(dir.path().to_string_lossy().to_string()));
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn open_project_succeeds_with_valid_directory() {
|
async fn open_project_succeeds_with_valid_directory() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
let path = dir.path().to_string_lossy().to_string();
|
let path = dir.path().to_string_lossy().to_string();
|
||||||
let payload = Json(PathPayload { path: path.clone() });
|
let payload = Json(PathPayload { path: path.clone() });
|
||||||
let result = api.open_project(payload).await.unwrap();
|
let result = api.open_project(payload).await.unwrap();
|
||||||
@@ -116,7 +117,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn open_project_fails_with_nonexistent_file_path() {
|
async fn open_project_fails_with_nonexistent_file_path() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
// Create a file (not a directory) to trigger validation error
|
// Create a file (not a directory) to trigger validation error
|
||||||
let file_path = dir.path().join("not_a_dir.txt");
|
let file_path = dir.path().join("not_a_dir.txt");
|
||||||
std::fs::write(&file_path, "content").unwrap();
|
std::fs::write(&file_path, "content").unwrap();
|
||||||
@@ -130,7 +131,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn close_project_returns_true() {
|
async fn close_project_returns_true() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
let result = api.close_project().await.unwrap();
|
let result = api.close_project().await.unwrap();
|
||||||
assert!(result.0);
|
assert!(result.0);
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn close_project_clears_current_project() {
|
async fn close_project_clears_current_project() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
|
|
||||||
// Verify project is set initially
|
// Verify project is set initially
|
||||||
let before = api.get_current_project().await.unwrap();
|
let before = api.get_current_project().await.unwrap();
|
||||||
@@ -155,7 +156,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_known_projects_returns_empty_initially() {
|
async fn list_known_projects_returns_empty_initially() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
// Close the project so the store has no known projects
|
// Close the project so the store has no known projects
|
||||||
api.close_project().await.unwrap();
|
api.close_project().await.unwrap();
|
||||||
let result = api.list_known_projects().await.unwrap();
|
let result = api.list_known_projects().await.unwrap();
|
||||||
@@ -165,7 +166,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_known_projects_returns_project_after_open() {
|
async fn list_known_projects_returns_project_after_open() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
let path = dir.path().to_string_lossy().to_string();
|
let path = dir.path().to_string_lossy().to_string();
|
||||||
|
|
||||||
api.open_project(Json(PathPayload { path: path.clone() }))
|
api.open_project(Json(PathPayload { path: path.clone() }))
|
||||||
@@ -179,7 +180,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn forget_known_project_removes_project() {
|
async fn forget_known_project_removes_project() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
let path = dir.path().to_string_lossy().to_string();
|
let path = dir.path().to_string_lossy().to_string();
|
||||||
|
|
||||||
api.open_project(Json(PathPayload { path: path.clone() }))
|
api.open_project(Json(PathPayload { path: path.clone() }))
|
||||||
@@ -202,7 +203,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn forget_known_project_returns_true_for_nonexistent_path() {
|
async fn forget_known_project_returns_true_for_nonexistent_path() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<ProjectApi>(&dir);
|
||||||
let result = api
|
let result = api
|
||||||
.forget_known_project(Json(PathPayload {
|
.forget_known_project(Json(PathPayload {
|
||||||
path: "/some/unknown/path".to_string(),
|
path: "/some/unknown/path".to_string(),
|
||||||
|
|||||||
+25
-29
@@ -104,27 +104,23 @@ pub fn get_editor_command_from_store(ctx: &AppContext) -> Option<String> {
|
|||||||
.and_then(|v| v.as_str().map(|s| s.to_string()))
|
.and_then(|v| v.as_str().map(|s| s.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
impl From<std::sync::Arc<AppContext>> for SettingsApi {
|
||||||
|
fn from(ctx: std::sync::Arc<AppContext>) -> Self {
|
||||||
|
Self { ctx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::http::context::AppContext;
|
use crate::http::test_helpers::{make_api, test_ctx};
|
||||||
use std::sync::Arc;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn test_ctx(dir: &TempDir) -> AppContext {
|
|
||||||
AppContext::new_test(dir.path().to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_api(dir: &TempDir) -> SettingsApi {
|
|
||||||
SettingsApi {
|
|
||||||
ctx: Arc::new(AppContext::new_test(dir.path().to_path_buf())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_editor_returns_none_when_unset() {
|
async fn get_editor_returns_none_when_unset() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
let result = api.get_editor().await.unwrap();
|
let result = api.get_editor().await.unwrap();
|
||||||
assert!(result.0.editor_command.is_none());
|
assert!(result.0.editor_command.is_none());
|
||||||
}
|
}
|
||||||
@@ -132,7 +128,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_editor_stores_command() {
|
async fn set_editor_stores_command() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
let payload = Json(EditorCommandPayload {
|
let payload = Json(EditorCommandPayload {
|
||||||
editor_command: Some("zed".to_string()),
|
editor_command: Some("zed".to_string()),
|
||||||
});
|
});
|
||||||
@@ -143,7 +139,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_editor_clears_command_on_null() {
|
async fn set_editor_clears_command_on_null() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
api.set_editor(Json(EditorCommandPayload {
|
api.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some("zed".to_string()),
|
editor_command: Some("zed".to_string()),
|
||||||
}))
|
}))
|
||||||
@@ -161,7 +157,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_editor_clears_command_on_empty_string() {
|
async fn set_editor_clears_command_on_empty_string() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
let result = api
|
let result = api
|
||||||
.set_editor(Json(EditorCommandPayload {
|
.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some(String::new()),
|
editor_command: Some(String::new()),
|
||||||
@@ -174,7 +170,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_editor_trims_whitespace_only() {
|
async fn set_editor_trims_whitespace_only() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
let result = api
|
let result = api
|
||||||
.set_editor(Json(EditorCommandPayload {
|
.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some(" ".to_string()),
|
editor_command: Some(" ".to_string()),
|
||||||
@@ -187,7 +183,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_editor_returns_value_after_set() {
|
async fn get_editor_returns_value_after_set() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
api.set_editor(Json(EditorCommandPayload {
|
api.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some("cursor".to_string()),
|
editor_command: Some("cursor".to_string()),
|
||||||
}))
|
}))
|
||||||
@@ -200,7 +196,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn editor_command_defaults_to_null() {
|
fn editor_command_defaults_to_null() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
let result = get_editor_command_from_store(&ctx);
|
let result = get_editor_command_from_store(&ctx);
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
@@ -208,7 +204,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn set_editor_command_persists_in_store() {
|
fn set_editor_command_persists_in_store() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
|
|
||||||
ctx.store.set(EDITOR_COMMAND_KEY, json!("zed"));
|
ctx.store.set(EDITOR_COMMAND_KEY, json!("zed"));
|
||||||
ctx.store.save().unwrap();
|
ctx.store.save().unwrap();
|
||||||
@@ -220,7 +216,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn get_editor_command_from_store_returns_value() {
|
fn get_editor_command_from_store_returns_value() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
|
|
||||||
ctx.store.set(EDITOR_COMMAND_KEY, json!("code"));
|
ctx.store.set(EDITOR_COMMAND_KEY, json!("code"));
|
||||||
let result = get_editor_command_from_store(&ctx);
|
let result = get_editor_command_from_store(&ctx);
|
||||||
@@ -230,7 +226,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn delete_editor_command_returns_none() {
|
fn delete_editor_command_returns_none() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
|
|
||||||
ctx.store.set(EDITOR_COMMAND_KEY, json!("cursor"));
|
ctx.store.set(EDITOR_COMMAND_KEY, json!("cursor"));
|
||||||
ctx.store.delete(EDITOR_COMMAND_KEY);
|
ctx.store.delete(EDITOR_COMMAND_KEY);
|
||||||
@@ -258,7 +254,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn get_editor_http_handler_returns_null_when_not_set() {
|
async fn get_editor_http_handler_returns_null_when_not_set() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
let api = SettingsApi {
|
let api = SettingsApi {
|
||||||
ctx: Arc::new(ctx),
|
ctx: Arc::new(ctx),
|
||||||
};
|
};
|
||||||
@@ -269,7 +265,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_editor_http_handler_stores_value() {
|
async fn set_editor_http_handler_stores_value() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
let api = SettingsApi {
|
let api = SettingsApi {
|
||||||
ctx: Arc::new(ctx),
|
ctx: Arc::new(ctx),
|
||||||
};
|
};
|
||||||
@@ -286,7 +282,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn set_editor_http_handler_clears_value_when_null() {
|
async fn set_editor_http_handler_clears_value_when_null() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let ctx = test_ctx(&dir);
|
let ctx = test_ctx(dir.path());
|
||||||
let api = SettingsApi {
|
let api = SettingsApi {
|
||||||
ctx: Arc::new(ctx),
|
ctx: Arc::new(ctx),
|
||||||
};
|
};
|
||||||
@@ -310,7 +306,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn open_file_returns_error_when_no_editor_configured() {
|
async fn open_file_returns_error_when_no_editor_configured() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
let result = api
|
let result = api
|
||||||
.open_file(Query("src/main.rs".to_string()), Query(Some(42)))
|
.open_file(Query("src/main.rs".to_string()), Query(Some(42)))
|
||||||
.await;
|
.await;
|
||||||
@@ -322,7 +318,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn open_file_spawns_editor_with_path_and_line() {
|
async fn open_file_spawns_editor_with_path_and_line() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
// Configure the editor to "echo" which is a safe no-op command
|
// Configure the editor to "echo" which is a safe no-op command
|
||||||
api.set_editor(Json(EditorCommandPayload {
|
api.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some("echo".to_string()),
|
editor_command: Some("echo".to_string()),
|
||||||
@@ -339,7 +335,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn open_file_spawns_editor_with_path_only_when_no_line() {
|
async fn open_file_spawns_editor_with_path_only_when_no_line() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
api.set_editor(Json(EditorCommandPayload {
|
api.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some("echo".to_string()),
|
editor_command: Some("echo".to_string()),
|
||||||
}))
|
}))
|
||||||
@@ -355,7 +351,7 @@ mod tests {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn open_file_returns_error_for_nonexistent_editor() {
|
async fn open_file_returns_error_for_nonexistent_editor() {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
let api = make_api(&dir);
|
let api = make_api::<SettingsApi>(&dir);
|
||||||
api.set_editor(Json(EditorCommandPayload {
|
api.set_editor(Json(EditorCommandPayload {
|
||||||
editor_command: Some("this_editor_does_not_exist_xyz_abc".to_string()),
|
editor_command: Some("this_editor_does_not_exist_xyz_abc".to_string()),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//! Shared test utilities for HTTP handler tests.
|
||||||
|
//!
|
||||||
|
//! Import with `use crate::http::test_helpers::{make_api, test_ctx};`
|
||||||
|
|
||||||
|
use crate::http::context::AppContext;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
/// Build an [`AppContext`] rooted at `dir` for use in tests.
|
||||||
|
pub(crate) fn test_ctx(dir: &Path) -> AppContext {
|
||||||
|
AppContext::new_test(dir.to_path_buf())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build an API struct rooted in `dir` for use in tests.
|
||||||
|
///
|
||||||
|
/// Requires the API type to implement `From<Arc<AppContext>>`. Add a
|
||||||
|
/// `#[cfg(test)]` impl block to each API struct to opt in.
|
||||||
|
pub(crate) fn make_api<T: From<Arc<AppContext>>>(dir: &TempDir) -> T {
|
||||||
|
Arc::new(test_ctx(dir.path())).into()
|
||||||
|
}
|
||||||
@@ -5,3 +5,5 @@ pub mod shell;
|
|||||||
pub mod story_metadata;
|
pub mod story_metadata;
|
||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test_helpers;
|
||||||
|
|||||||
@@ -74,17 +74,10 @@ fn needs_project_toml(story_kit: &Path) -> bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::io::test_helpers::setup_project;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn setup_project(dir: &TempDir) -> std::path::PathBuf {
|
|
||||||
let root = dir.path().to_path_buf();
|
|
||||||
let sk = root.join(".storkit");
|
|
||||||
fs::create_dir_all(sk.join("specs").join("tech")).unwrap();
|
|
||||||
fs::create_dir_all(root.join("script")).unwrap();
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── needs_onboarding ──────────────────────────────────────────
|
// ── needs_onboarding ──────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -64,18 +64,12 @@ pub async fn search_files_impl(query: String, root: PathBuf) -> Result<Vec<Searc
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fs;
|
use crate::io::test_helpers::create_test_files;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn setup_project(files: &[(&str, &str)]) -> TempDir {
|
fn setup_project(files: &[(&str, &str)]) -> TempDir {
|
||||||
let dir = TempDir::new().unwrap();
|
let dir = TempDir::new().unwrap();
|
||||||
for (path, content) in files {
|
create_test_files(&dir, files);
|
||||||
let full = dir.path().join(path);
|
|
||||||
if let Some(parent) = full.parent() {
|
|
||||||
fs::create_dir_all(parent).unwrap();
|
|
||||||
}
|
|
||||||
fs::write(full, content).unwrap();
|
|
||||||
}
|
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//! Shared test utilities for I/O module tests.
|
||||||
|
//!
|
||||||
|
//! Import with `use crate::io::test_helpers::{create_test_files, setup_project};`
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
/// Create a minimal storkit project directory structure under `dir`.
|
||||||
|
///
|
||||||
|
/// Creates `.storkit/specs/tech/` and `script/`, then returns the root path.
|
||||||
|
/// Used by onboarding and wizard tests.
|
||||||
|
pub(crate) fn setup_project(dir: &TempDir) -> PathBuf {
|
||||||
|
let root = dir.path().to_path_buf();
|
||||||
|
let sk = root.join(".storkit");
|
||||||
|
fs::create_dir_all(sk.join("specs").join("tech")).unwrap();
|
||||||
|
fs::create_dir_all(root.join("script")).unwrap();
|
||||||
|
root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a set of files into `dir` at the given relative paths.
|
||||||
|
///
|
||||||
|
/// Parent directories are created automatically. Used by search tests.
|
||||||
|
pub(crate) fn create_test_files(dir: &TempDir, files: &[(&str, &str)]) {
|
||||||
|
for (path, content) in files {
|
||||||
|
let full = dir.path().join(path);
|
||||||
|
if let Some(parent) = full.parent() {
|
||||||
|
fs::create_dir_all(parent).unwrap();
|
||||||
|
}
|
||||||
|
fs::write(full, content).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -255,15 +255,9 @@ pub fn format_wizard_state(state: &WizardState) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::io::test_helpers::setup_project;
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn setup_project(dir: &TempDir) -> std::path::PathBuf {
|
|
||||||
let root = dir.path().to_path_buf();
|
|
||||||
let sk = root.join(".storkit");
|
|
||||||
std::fs::create_dir_all(&sk).unwrap();
|
|
||||||
root
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn default_state_has_all_steps_pending() {
|
fn default_state_has_all_steps_pending() {
|
||||||
let state = WizardState::default();
|
let state = WizardState::default();
|
||||||
|
|||||||
Reference in New Issue
Block a user